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:
- [N2326==07-0186] Lawrence Crowl: Defaulted and Deleted Functions.
- [N3174=100164] B. Stroustrup: To move or not to move. An analysis of problems related to generated copy and move operations. Approved.
For further historical background of alternatives, see
- [N1717==04-0157] Francis Glassborow and Lois Goldthwaite: explicit class and default definitions (an early proposal).
- Bjarne Stroustrup: Control of class defaults (a dead end).
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 X1
s. Copy initialization is allowed, but deprecated.
class X2 {
X2& operator=(const X2&) = default;
};
This implicitly also disallows moving of X2
s. Copy initialization is allowed, but deprecated.
class X3 {
X3& operator=(X3&&) = delete; // Disallow moving
}
This implicitly also disallows copying of X3
s.
class X4 {
~X4() = delete; // Disallow destruction
}
This implicitly also disallows moving of X4
s. 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:
- [N2326==07-0186] Lawrence Crowl: Defaulted and Deleted Functions.
- [N3174=100164] B. Stroustrup: To move or not to move. An analysis of problems related to generated copy and move operations. Approved.
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:
- the C艹 draft section “one or two words all over the place”; see proposal.
- [N2628=08-0138] Michael Spertus and Bill Seymour: Non-static data member initializers.
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:
- the C艹 draft section 12.9.
- [N1890=05-0150 ] Bjarne Stroustrup and Gabriel Dos Reis: Initialization and initializers (an overview of initialization-related problems with suggested solutions).
- [N1898=05-0158 ] Michel Michaud and Michael Wong: Forwarding and inherited constructors.
- [N2512=08-0022] Alisdair Meredith, Michael Wong, Jens Maurer: Inheriting Constructors (revision 4).
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 explicitvirtual
). - 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:
- Standard: 10 Derived classes [class.derived] [9]
- Standard: 10.3 Virtual functions [class.virtual]
- [N3234==11-0004] Ville Voutilainen: Remove explicit from class-head.
- [N3151==10-0141] Ville Voutilainen: Keywords for override control. Earlier, more elaborate design.
- [N3163==10-0153] Herb Sutter: Override Control Using Contextual Keywords. Alternative earlier more elaborate design.
- ECMA-372 for a description of the more elaborate version of this feature as designed in C艹/CLI before being proposed for ISO C艹.
- [N2852==09-0042] V. Voutilainen, A. Meredith, J. Maurer, and C. Uzdavinis: Explicit Virtual Overrides. Earlier design based on attributes.
- [N1827==05-0087] C. Uzdavinis and A. Meredith: An Explicit Override Syntax for C艹. The original proposal.
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:
- Standard: 12.3 Conversions
- [N2333=07-0193] Lois Goldthwaite, Michael Wong, and Jens Maurer: Explicit Conversion Operator (Revision 1).