cpp11-language-classes

Save to:
Instapaper Pocket Readability

C++11 Language Extensions – Classes

=default and =delete

The common idiom of “prohibiting copying” can now be expressed directly:

    class X {
        // ...

        X& operator=(const X&) = delete;    // Disallow copying
        X(const X&) = delete;
    };

Conversely, we can also say explicitly that we want to default copy behavior:

    class Y {
        // ...
        Y& operator=(const Y&) = default;   // default copy semantics
        Y(const Y&) = default;
    };

Explicitly writing out the default by hand is at best redundant, and has two drawbacks: it sometimes generates less efficient code than the compiler-generated default would, and it prevents types from being considered PODs. However, comments about copy operations and (worse) a user explicitly defining copy operations meant to give the default behavior were not uncommon in pre-C++11 code. Leaving it to the compiler to implement the default behavior is simpler, less error-prone, and often leads to better object code.

The =default mechanism can be used for any function that has a default. The =delete mechanism can be used for any function. For example, we can eliminate an undesired conversion like this:

    struct Z {
        // ...

        Z(long long);     // can initialize with a long long
        Z(long) = delete; // but not anything smaller
    };

See also:

For further historical background of alternatives, see

Control of default move and copy

By default, a class has five operations:

  • copy assignment
  • copy constructor
  • move assignment
  • move constructor
  • destructor

If you declare any of those you must consider all and explicitly define or =default the ones you want. Think of copying, moving, and destruction as closely related operations, rather than individual operations that you can freely mix and match – you can specify arbitrary combinations, but only a few combinations make sense semantically.

If any move, copy, or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, no move is generated by default. If any copy or destructor is explicitly specified (declared, defined, =default, or =delete) by the user, any undeclared copy operations are generated by default, but this is deprecated, so don’t rely on that. For example:

    class X1 {
        X1& operator=(const X1&) = delete;  // Disallow copying
    };

This implicitly also disallows moving of X1s. Copy initialization is allowed, but deprecated.

    class X2 {
        X2& operator=(const X2&) = default;
    };

This implicitly also disallows moving of X2s. Copy initialization is allowed, but deprecated.

    class X3 {
        X3& operator=(X3&&) = delete;   // Disallow moving
    }

This implicitly also disallows copying of X3s.

    class X4 {
        ~X4() = delete; // Disallow destruction
    }

This implicitly also disallows moving of X4s. Copying is allowed, but deprecated.

If you declare one of these five function, you should explicitly declare all. For example:

    template<class T>
    class Handle {
        T* p;
    public:
        Handle(T* pp) : p{pp} {}
        ~Handle() { delete p; }     // user-defined destructor: no implicit copy or move 

        Handle(Handle&& h) :p{h.p} { h.p=nullptr; }     // transfer ownership
        Handle& operator=(Handle&& h) { delete p; p=h.p; h.p=nullptr; return *this; }   // transfer ownership

        Handle(const Handle&) = delete;     // no copy
        Handle& operator=(const Handle&) = delete;

        // ...
    };

See also:

Delegating constructors

In C++98, if you want two constructors to do the same thing, repeat yourself or call “an init() function.” For example:

    class X {
        int a;
        void validate(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }
    public:
        X(int x) { validate(x); }
        X() { validate(42); }
        X(string s) { int x = lexical_cast<int>(s); validate(x); }
        // ...
    };

Verbosity hinders readability and repetition is error-prone. Both get in the way of maintainability. So, in C++11, we can define one constructor in terms of another:

    class X {
        int a;
    public:
        X(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }
        X() :X{42} { }
        X(string s) :X{lexical_cast<int>(s)} { }
        // ...
    };

See also:

  • the C++ draft section 12.6.2
  • N1986==06-0056 Herb Sutter and Francis Glassborow: Delegating Constructors (revision 3).
  • ECMA-372 for a description of this feature as originally designed in C++/CLI before being proposed for ISO C++.

In-class member initializers

In C++98, only static const members of integral types could be initialized in-class, and the initializer has to be a constant expression. These restrictions ensured that the compiler can do the initialization at compile-time. For example:

    int var = 7;

    class X {
        static const int m1 = 7;        // ok
        const int m2 = 7;                   // error: not static
        static int m3 = 7;              // error: not const
        static const int m4 = var;          // error: initializer not constant expression
        static const string m5 = "odd"; // error: not integral type
        // ...
    };

The basic idea for C++11 was to allow a non-static data member to be initialized where it is declared (in its class). A constructor can then use the initializer when run-time initialization is needed. Consider:

    class A {
    public:
        int a = 7;
    };

This is equivalent to:

    class A {
    public:
        int a;
        A() : a(7) {}
    };

This saves a bit of typing, but the real benefits come in classes with multiple constructors. Often, all constructors use a common initializer for a member:

    class A {
    public:
        A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {}
        A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {}
        A(D d) : a(7), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {}
        int a, b;
    private:
        HashingFunction hash_algorithm;  // Cryptographic hash to be applied to all A instances
        std::string s;                   // String indicating state in object lifecycle
    };

The fact that hash_algorithm and s each have a single default is lost in the mess of code and could easily become a problem during maintenance. Instead, we can factor out the initialization of the data members:

    class A {
    public:
        A(): a(7), b(5) {}
        A(int a_val) : a(a_val), b(5) {}
        A(D d) : a(7), b(g(d)) {}
        int a, b;
    private:
        HashingFunction hash_algorithm{"MD5"};  // Cryptographic hash to be applied to all A instances
        std::string s{"Constructor run"};       // String indicating state in object lifecycle
    };

If a member is initialized by both an in-class initializer and a constructor, only the constructor’s initialization is done (it “overrides” the default). So we can simplify further:

    class A {
    public:
        A() {}
        A(int a_val) : a(a_val) {}
        A(D d) : b(g(d)) {}
        int a = 7;
        int b = 5;  
    private:
        HashingFunction hash_algorithm{"MD5"};  // Cryptographic hash to be applied to all A instances
        std::string s{"Constructor run"};       // String indicating state in object lifecycle
    };

See also:

Inherited constructors

People sometimes are confused about the fact that ordinary scope rules apply to class members. In particular, a member of a base class is not in the same scope as a member of a derived class:

    struct B {
        void f(double);
    };

    struct D : B {
        void f(int);
    };

    B b;   b.f(4.5);    // fine
    D d;   d.f(4.5);    // surprise: calls f(int) with argument 4

In C++98, we can “lift” a set of overloaded functions from a base class into a derived class:

    struct B {
        void f(double);
    };

    struct D : B {
        using B::f;     // bring all f()s from B into scope
        void f(int);    // add a new f()
    };

    B b;   b.f(4.5);    // fine
    D d;   d.f(4.5);    // fine: calls D::f(double) which is B::f(double)

Stroustrup has said that “Little more than a historical accident prevents using this to work for a constructor as well as for an ordinary member function.” C++11 provides that facility:

    class Derived : public Base { 
    public: 
        using Base::f;    // lift Base's f into Derived's scope -- works in C++98
        void f(char);     // provide a new f 
        void f(int);      // prefer this f to Base::f(int) 

        using Base::Base; // lift Base constructors Derived's scope -- new in C++11
        Derived(char);    // provide a new constructor 
        Derived(int);     // prefer this constructor to Base::Base(int) 
        // ...
    }; 

If you so choose, you can still shoot yourself in the foot by inheriting constructors in a derived class in which you define new member variables needing initialization:

    struct B1 {
        B1(int) { }
    };

    struct D1 : B1 {
        using B1::B1; // implicitly declares D1(int)
        int x;
    };

    void test()
    {
        D1 d(6);    // Oops: d.x is not initialized
        D1 e;       // error: D1 has no default constructor
    }

You might remove the bullet from your foot by using a member-initializer:

        struct D1 : B1 {
            using B1::B1;   // implicitly declares D1(int)
            int x{0};   // note: x is initialized
        };

        void test()
        {
            D1 d(6);    // d.x is zero
        }

See also:

Override controls: override

No special keyword or annotation is needed for a function in a derived class to override a function in a base class. For example:

    struct B {
        virtual void f();
        virtual void g() const;
        virtual void h(char);
        void k();   // not virtual
    };

    struct D : B {
        void f();   // overrides B::f()
        void g();   // doesn't override B::g() (wrong type)
        virtual void h(char);   // overrides B::h()
        void k();   // doesn't override B::k() (B::k() is not virtual)
    };

This can cause confusion (what did the programmer mean?), and problems if a compiler doesn’t warn against suspicious code. For example,

  • Did the programmer mean to override B::g()? (almost certainly yes).
  • Did the programming mean to override B::h(char)? (probably not because of the redundant explicit virtual).
  • Did the programmer mean to override B::k()? (probably, but that’s not possible).

To allow the programmer to be more explicit about overriding, we now have the “contextual keyword” override:

    struct D : B {
        void f() override;  // OK: overrides B::f()
        void g() override;  // error: wrong type
        virtual void h(char);   // overrides B::h(); likely warning
        void k() override;  // error: B::k() is not virtual
    };

A declaration marked override is only valid if there is a function to override. The problem with h() is not guaranteed to be caught (because it is not an error according to the language definition) but it is easily diagnosed.

override is only a contextual keyword, so you can still use it as an identifier:

int override = 7;   // not recommended

See also:

Override controls: final

Sometimes, a programmer wants to prevent a virtual function from being overridden. This can be achieved by adding the specifier final. For example:

    struct B {
        virtual void f() const final;   // do not override
        virtual void g();
    };

    struct D : B {
        void f() const;     // error: D::f attempts to override final B::f
        void g();       // OK
    };

There are legitimate reasons for wanting to prevent overriding, but it should be noted that many examples used to motivate final have been based on mistaken assumptions on how expensive virtual functions are (usually based on experience with other languages). So, if you feel the urge to add a final specifier, please double check that the reason is logical: Would semantic errors be likely if someone defined a class that overrode that virtual function? Adding final closes the possibility that a future user of the class might provide a better implementation of the function for some class you haven’t thought of. If you don’t want to keep that option open, why did you define the function to be virtual in the first place? Most reasonable answers to that question encountered to date have been along the lines: This is a fundamental function in a framework that the framework builders needed to override but isn’t safe for general users to override. Be suspicious towards such claims and be sure final is really appropriate.

If it is performance (inlining) you want or you simply never want to override, it is typically better not to define a function to be virtual in the first place. This is not Java.

final is only a contextual keyword, so you can still use it as an identifier:

int final = 7;  // not recommended

See also:

  • Standard: 10 Derived classes [class.derived] [9]
  • Standard: 10.3 Virtual functions [class.virtual]

Explicit conversion operators

C++98 provides implicit and explicit constructors; that is, the conversion defined by a constructor declared explicit can be used only for explicit conversions whereas other constructors can be used for implicit conversions also. For example:

    struct S { S(int); };   // "ordinary constructor" defines implicit conversion
    S s1(1);        // ok
    S s2 = 1;   // ok
    void f(S);
    f(1);       // ok (but that's often a bad surprise -- what if S was vector?)

    struct E { explicit E(int); };  // explicit constructor
    E e1(1);        // ok
    E e2 = 1;   // error (but that's often a surprise)
    void f(E);
    f(1);       // error (protects against surprises -- e.g. std::vector's constructor from int is explicit)

However, a constructor is not the only mechanism for defining a conversion. If we can’t modify a class, we can define a conversion operator from a different class. For example:

    struct S { S(int) { } /* ... */ };

    struct SS {
        int m;
        SS(int x) :m(x) { }
        operator S() { return S(m); }  // because S don't have S(SS); non-intrusive
    };

    SS ss(1);
    S s1 = ss;  // ok; like an implicit constructor
    S s2(ss);   // ok ; like an implicit constructor
    void f(S);
    f(ss);      // ok; like an implicit constructor

Unfortunately, C++98 had no explicit conversion operators, largely because there are far fewer problematic examples. C++11 deals with that oversight by allowing conversion operators to be explicit. For example:

    struct S { S(int) { } };

    struct SS {
        int m;
        SS(int x) :m(x) { }
        explicit operator S() { return S(m); }  // because S don't have S(SS)
    };

    SS ss(1);
    S s1 = ss;  // error; like an explicit constructor
    S s2(ss);   // ok ; like an explicit constructor
    void f(S); 
    f(ss);      // error; like an explicit constructor

See also: