Constructor

Classes control default initialization by defining a special constructor, known as the default constructor. The default constructor is one that takes no arguments.

If our class does not explicitly define any constructors, the compiler will implicitly define the default constructor for us. The compiler-generated constructor is known as the synthesized default constructor. For most classes, this synthesized constructor initializes each data member of the class as follows:

  • If there is an in-class initializer, use it to initialize the member.
  • Otherwise, default-initialize the member.

The compiler generates a default constructor automatically only if a class declares no constructors.

class A
{
public:
    A()=default;
};

First, note that this constructor defines the default constructor because it takes no arguments1. We are defining this constructor only because we want to provide other constructors as well as the default constructor. We want this constructor to do exactly the same work as the synthesized version we had been using.

Under the new standard, if we want the default behavior, we can ask the compiler to generate the constructor for us by writing = default after the parameter list. The = default can appear with the declaration inside the class body or on the definition outside the class body. Like any other function, if the = default appears inside the class body, the default constructor will be inlined; if it appears on the definition outside the class, the member will not be inlined by default.

几种构造函数的签名(包含赋值运算符):

// cf. https://learn.microsoft.com/zh-cn/cpp/cpp/constructors-cpp?view=msvc-170
class Box2
{
public:
    Box2() = delete;  // default ctor
    Box2(const Box2& other) = default;  // copy ctor
    Box2& operator=(const Box2& other) = default;  // copy assignment
    Box2(Box2&& other) = default;  // move ctor
    Box2& operator=(Box2&& other) = default;  // move assignment
    //...
};

Constructor Initializer List

struct Vector3
{
    Vector3()=default;
    Vector3(float _x, float _y, float _z)
        : x(_x), y(_y), z(_z) {}  // ctor initializer list
    int x, y, z;
}

这就叫Constructor Initializer List.

It is usually best for a constructor to use an in-class initializer if one exists and gives the member the correct value.

Constructors should not override in-class initializers except to use a different initial value. If you can’t use in-class initializers, each constructor should explicitly initialize every member of built-in type.

Revisited

When we define variables, we typically initialize them immediately rather than defining them and then assigning to them:

string foo = "Hello World!"; // define and initialize
string bar; // default initialized to the empty string
bar = "Hello World!"; // assign a new value to bar

Exactly the same distinction between initialization and assignment applies to the data members of objects. If we do not explicitly initialize a member in the constructor initializer list, that member is default initialized before the constructor body starts executing.

As a result, see below:

We must use the constructor initializer list to provide values for members that are const, reference, or of a class type that does not have a default constructor.

Delegating Constructors (since c++11)

A delegating constructor uses another constructor from its own class to perform its initialization. It is said to “delegate” some (or all) of its work to this other constructor. For example:

class Sales_data {
public:
// nondelegating constructor initializes members from corresponding arguments
Sales_data(std::string s, unsigned cnt, double price):
bookNo(s), units_sold(cnt), revenue(cnt*price) { }
// remaining constructors all delegate to another constructor
Sales_data(): Sales_data("", 0, 0) {}
Sales_data(std::string s): Sales_data(s, 0,0) {}
Sales_data(std::istream &is): Sales_data()
{ read(is, *this); }
// other members as before
};

Implicit Class-Type Conversions

Every constructor that can be called with a single argument defines an implicit conversion to a class type. Such constructors are sometimes referred to as converting constructors.

A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type.

Note: Only One Class-Type Conversion Is Allowed. 只能做一步转换,否则调不到对应构造函数。如下例

#include <iostream>
#include <vector>
#include <string>
 
using namespace std;
 
struct A
{
    A(string _s): s(_s) { cout << "A::ctor called\n"; }
    string s;
};
 
 
int main()
{
    vector<A> a;
    a.push_back(string("abc"));  // valid, implicit call A::A(string)
    a.push_back("def");  // invalid, need two conversion, char*->string, string->A
    return 0;
}

如果不想要这种转换,使用explicit关键字阻止之。

Suppressing Implicit Conversions Defined by Constructors

We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit:

struct A
{
    explicit A(string _s): s(_s) { cout << "A::ctor called\n"; }
    string s;
};
 
 
int main()
{
    vector<A> a;
    a.push_back(string("abc"));  // error, A::A(string) is now explicit
    return 0;
}

意思是用explict修饰的构造函数,必须显式的调用。

The explicit keyword is meaningful only on constructors that can be called with a single argument.

When a constructor is declared explicit, it can be used only with the direct form of initialization (§ 3.2.1, p. 84). Moroever, the compiler will not use this constructor in an automatic conversion.

Member initialization

class A
{
    int a = 1;  // 简单数据成员的in-class initializer
    vector<int> b = vector<int>(1, 2, 3); // 复杂数据成员的in-class intializer
    vector<int> c {4, 5, 6}; // since c++11
    vector<int> d = {7, 8, 9}; // not recommended
};

As we’ve seen, in-class initializers must use either the = form of initialization (which we used when we initialized the the data members of Screen) or the direct form of initialization using curly braces (as we do for screens).

When we provide an in-class initializer, we must do so following an = sign or inside braces.

原本类的数据成员的定义和初始化是分开的,成员定义在类中,成员初始化由构造函数负责。而现在提供一种叫做”in-class initializer”的方式,它的语法如上所述。

所以,不要纳闷为啥在成员声明中不能直接调用构造函数。

struct Vector3
{
    Vector3()=default;
    Vector3(float _x, float _y, float _z)
        : x(_x), y(_y), z(_z) {}
    int x, y, z;
};
 
struct A
{
    Vector3 pos {1,2,3};
    Vector3 velocity = Vector3(4,5,6);
    Vector3 v(7,8,9); // invalid syntax
};
 
int main()
{
    Vector3 v(1,2,3);  // valid, 定义变量v
    return 0;
}

因为你在试图使用in-class initializer,但你没有遵循它的格式(a = sign or inside braces)。

Constructors, Destructors, and Assignment Operators

If you don’t declare them yourself, compilers will declare their own versions of a copy constructor, a copy assignment operator, and a destructor. Furthermore, if you declare no constructors at all, compilers will also declare a default constructor for you. All these functions will be both public and inline.

As a result, if you write

class Empty{};

it’s essentially the same as if you’d written this:

class Empty {
public:
    Empty() { ... } // default constructor
    Empty(const Empty& rhs) { ... } // copy constructor
    ~Empty() { ... } // destructor — see below
                     // for whether it’s virtual
    Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};

These functions are generated only if they are needed, the following code will cause each function to be generated:

Empty e1;     // default constructor;
              // destructor
Empty e2(e1); // copy constructor
e2 = e1;      // copy assignment operator

Things to Remember ✦ Compilers may implicitly generate a class’s default constructor, copy constructor, copy assignment operator, and destructor.

C++98有4个特殊非静态成员函数:

  • 默认构造函数:没有任何其他构造函数时默认提供
  • 拷贝构造函数:用户不提供时默认提供
  • 拷贝赋值运算符:用户不提供时默认提供
  • 析构函数:用户不提供时默认提供

三法则由是呼之而出:

三法则

如果某个类需要用户定义的析构函数,用户定义的拷贝构造函数或用户定义的拷贝赋值运算符,则它几乎肯定三者全部都需要。

调用时机(from cpprefernce)

COPY CONSTRUCTOR

The copy constructor is called whenever an object is initialized (by direct-initialization or copy-initialization) from another object of the same type (unless overload resolution selects a better match or the call is elided), which includes

  • initialization: T a = b; or T a(b);, where b is of type T;
  • function argument passing: f(a);, where a is of type T and f is void f(T t);
  • function return: return a; inside a function such as T f(), where a is of type T, which has no move constructor.

MOVE CONSTRUCTOR

The move constructor is typically called when an object is initialized (by direct-initialization or copy-initialization) from rvalue (xvalue or prvalue) (until C++17)xvalue (since C++17) of the same type, including

  • initialization: T a = std::move(b); or T a(std::move(b));, where b is of type T;
  • function argument passing: f(std::move(a));, where a is of type T and f is void f(T t);
  • function return: return a; inside a function such as T f(), where a is of type T which has a move constructor.

COPY ASSIGNMENT

The copy assignment operator is called whenever selected by overload resolution, e.g. when an object appears on the left side of an assignment expression.

MOVE ASSIGNMENT

The move assignment operator is called whenever it is selected by overload resolution, e.g. when an object appears on the left-hand side of an assignment expression, where the right-hand side is an rvalue of the same or implicitly convertible type.

注意T a = b;是一个initialization expression而不是assignment expression.

Prevent copies for your object

  1. 「将构造函数显式声明为私有」By declaring a member function explicitly, you prevent compilers from generating their own version, and by making the function private, you keep people from calling it.
  2. 「将copy ctor声明为delete(since c++11)」。
class HomeForSale {
public:
    ...
    HomeForSale(const HomeForSale&)=delete;
    HomeForSale& operator=(const HomeForSale&)=delete;
};

第一种方法常见用法:

class HomeForSale {
public:
    ...
private:
    ...
    HomeForSale(const HomeForSale&); // declarations only
    HomeForSale& operator=(const HomeForSale&);
};

With the above class definition, compilers will thwart client attempts to copy HomeForSale objects, and if you inadvertently try to do it in a member or a friend function, the linker will complain.

It’s possible to move the link-time error up to compile time (always a good thing — earlier error detection is better than later) by declaring the copy constructor and copy assignment operator private not in HomeForSale itself, but in a base class specifically designed to prevent copying. The base class is simplicity itself:

class Uncopyable {
protected: // allow construction
    Uncopyable() {} // and destruction of
    ~Uncopyable() {} // derived objects...
private:
    Uncopyable(const Uncopyable&); // ...but prevent copying
    Uncopyable& operator=(const Uncopyable&);
};

To keep HomeForSale objects from being copied, all we have to do now is inherit from Uncopyable:

class HomeForSale: private Uncopyable { // class no longer
    ...                                 // declares copy ctor or
};                                      // copy assign. operator

This works, because compilers will try to generate a copy constructor and a copy assignment operator if anybody — even a member or friend function — tries to copy a HomeForSale object. As Item 12 explains, the compiler-generated versions of these functions will try to call their base class counterparts, and those calls will be rejected, because the copying operations are private in the base class.

Declare destructors virtual in polymorphic base classes

see also When should my destructor be virtual.

Deleting a derived class object through a pointer to a base class with a non-virtual destructor results in undefined behavior.

Things to Remember ✦ Polymorphic base classes should declare virtual destructors. If a class has any virtual functions, it should have a virtual destructor. ✦ Classes not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors. 会增加空间开销

不要在析构函数里面抛异常! Things to Remember ✦ Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program. ✦ If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular (i.e., non-destructor) function that performs the operation.

Handle assignment to self in operator=

An assignment to self occurs when an object is assigned to itself:

class Widget { ... };
Widget w;
...
w = w; // assignment to self

This looks silly, but it’s legal, so rest assured that clients will do it. Besides, assignment isn’t always so recognizable. For example,

a[i] = a[j]; // potential assignment to self

is an assignment to self if i and j have the same value, and

*px = *py; // potential assignment to self

is an assignment to self if px and py happen to point to the same thing.

Suppose you create a class that holds a raw pointer to a dynamically allocated bitmap

class Bitmap { ... };
class Widget {
...
private:
    Bitmap *pb; // ptr to a heap-allocated object
};

Here’s an implementation of operator= that looks reasonable on the surface but is unsafe in the presence of assignment to self. (It’s also not exception-safe, but we’ll deal with that in a moment.)

Widget&
Widget::operator=(const Widget& rhs) // unsafe impl. of operator=
{
    delete pb; // stop using current bitmap
    pb = new Bitmap(*rhs.pb); // start using a copy of rhs’s bitmap
    return *this;
}

The self-assignment problem here is that inside operator=, *this (the target of the assignment) and rhs could be the same object. When they are, the delete not only destroys the bitmap for the current object, it destroys the bitmap for rhs, too. At the end of the function, the Widget — which should not have been changed by the assignment to self — finds itself holding a pointer to a deleted object!

The traditional way to prevent this error is to check for assignment to self via an identity test at the top of operator=:

Widget& Widget::operator=(const Widget& rhs)
{
    if (this == &rhs) return *this; // identity test: if a self-assignment,
    // do nothing
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;

References

  1. C++ Primer, 3rd; ch7
  2. Effective C++, 3rd Edition; ch2

Footnotes

  1. A constructor that supplies default arguments for all its parameters also defines the default constructor.