C++ Primer 第 5 版中发现的错误(复制初始化与直接初始化)

Error spotted in C++ Primer 5th Edition (copy intialization vs direct initialization)

您好,我正在尝试了解复制构造函数的工作原理并查看示例。示例如下:

{//new scope
Sales_data *p = new Sales_data;
auto p2 = make_shared<Saled_data>();
Sales_data item(*p); // copy constructor copies *p into item
vector<Sales_data> vec;
vec.push_back(*p2);// copies the object to which p2 points
delete p;
}

我的问题是:

  1. 为什么写成“copy constructor copies *p into item”?我的意思是,item 是直接初始化的。如果我们写 Sales_data item = *p; 那么它将被称为复制初始化,那么为什么他们在评论中写了复制构造函数 copies *p into item.

现在,为了自己验证这一点,我尝试自己创建一个简单的示例,但我也无法正确理解这个概念。我的自定义示例如下:

#include<iostream>
#include<string>

class MAINCLASS{
  private:
    std::string name;
    int age =0;
  public:
    MAINCLASS(){
        std::cout<<"This is default initialization"<<std::endl;
    }
    MAINCLASS(MAINCLASS &obj){
        std::cout<<"This is direct initialization"<<std::endl;
    }
    MAINCLASS(const MAINCLASS &obj):name(obj.name),age(obj.age){
        std::cout<<"This is copy initialization"<<std::endl;
    }
};

int main(){
    MAINCLASS objectone;
    MAINCLASS objecttwo =objectone;
    MAINCLASS objectthree(objectone);
    return 0;
}

现在当我 运行 这个程序时,我得到以下输出:

This is defalut initialization

This is direct initialization

This is direct initialization

我从这个程序中得到的问题如下:

  1. 为什么在我写 MAINCLASS objecttwo =objectone; 时,在第二种情况下 没有得到 输出“这是复制初始化”?我读过在直接初始化函数中使用匹配,在复制构造函数中,我们将右手操作数成员复制到左手操作数成员中。所以当我写 MAINCLASS objecttwo =objectone; 时,它应该调用复制构造函数并在屏幕上打印“这是复制初始化”。但它是直接初始化对象。这里发生了什么?

复制初始化和直接初始化是根据构造时使用的语法
参见 Confusion in copy initialization and direct initialization

调用哪个构造函数是基于重载决议(而不是要构造的语法) 编译器调用最匹配传递的参数和定义参数的函数。

在您的示例中,由于 objectone 是非常量,因此最佳匹配是具有非常量参数的复制构造函数。由于另一个复制构造函数有一个 const& 参数,它将为一个 const 对象调用。

重写您的示例:

#include<iostream>
#include<string>

class MAINCLASS {
private:
    std::string name;
    int age = 0;
public:
    MAINCLASS() {
        std::cout << "This is default initialization" << std::endl;
    }
    MAINCLASS(MAINCLASS& obj) {
        std::cout << "This is copy constructor with non-const reference parameter" << std::endl;
    }
    MAINCLASS(const MAINCLASS& obj) :name(obj.name), age(obj.age) {
        std::cout << "This is copy constructor with const reference parameter" << std::endl;
    }
};

int main() {
    MAINCLASS objectone;
    const MAINCLASS const_objectone;

    MAINCLASS objecttwo = objectone;  // copy initialization of non-const object
    MAINCLASS objectthree(objectone); // direct initialization of non-const object

    MAINCLASS objectfour = const_objectone; // copy initialization of const object
    MAINCLASS objectfive(const_objectone);  // direct initialization of const object
    return 0;
}

输出将是:

This is default initialization
This is default initialization
This is copy constructor with non-const reference parameter
This is copy constructor with non-const reference parameter
This is copy constructor with const reference parameter
This is copy constructor with const reference parameter

不要混淆复制构造和复制初始化。您可以使用直接或复制初始化来复制构造。

Copy initialisation指的是一组初始化语法和语义。这包括 T a = b 语法。

copy constructor 是一种特殊的 class 方法,它采用上述 class 的参数。此方法应该只接受一个参数(T&const T& 都可以)。复制构造发生在那个函数被调用的时候。

考虑到这一点,我们可以继续回答您的问题。

  1. Why it is written that "copy constructor copies *p into item"? I mean, item is direct initialized. If we would have written Sales_data item = *p; then it will be called copy initialized...

Sales_data item = *pSales_data item(*p)都调用了复制构造函数。但是,前者使用复制初始化T a = b),而后者使用直接初始化T a(b))。

  1. Why are we not getting the output "this is copy initialization" in the second case when i write MAINCLASS objecttwo =objectone;?

其实这里的问题不在于copy/direct是否初始化。这是 lvalue/rvalue 重载解析的问题。

考虑以下程序:

#include <iostream>

void f(int& i) { std::cout << "int&\n"; }
void f(const int& i) { std::cout << "const int&\n"; }

int main() {
    f(1); // f(const int&)
    
    int i = 2;
    f(i); // f(int&)
}

f 根据传递的值是左值还是右值来选择。在第一种情况下,1 是右值,因此调用 f(const int&)(请参阅 )。在第二种情况下,i 是左值,选择 f(int&) 因为它更通用。

所以在你的例子中,MAINCLASS objecttwo =objectone;MAINCLASS objectthree(objectone); 都调用了 复制构造函数 。同样,前者使用复制初始化,而后者使用直接初始化。只是这两个调用都选择了非常量引用重载:MAINCLASS(MAINCLASS&).

尽管名称选择不当,但复制初始化与复制构造函数是正交的。

复制构造函数是任何构造函数,其第一个参数是对其 class 类型的左值引用,并且可以只用一个参数调用。它只是一个可以从现有对象初始化新对象的构造函数。这就是它的全部内容。您声明的两个构造函数实际上都是复制构造函数。这个也太

MAINCLASS(MAINCLASS volatile &obj, void *cookie = nullptr) {
  // .. Do something
  // This is a copy c'tor since this is valid:
  // MAINCLASS volatile vo;
  // MAINCLASS copy1_vo(vo);
}

正如其他答案所述,复制初始化只是一系列初始化上下文的名称。它包括涉及 = 的初始化、将参数传递给函数、return 语句和抛出表达式(我可能忘记了一些东西)。直接初始化涉及 other 个上下文。

拷贝构造函数可以用在上面的任何一个中。无论是复制初始化还是直接初始化。两者之间的区别——作为构造函数的附属物——是构造函数重载集的构建方式。复制初始化不使用显式声明的构造函数。例如,在这个例子中

struct Example {
  Example() = default;
  explicit Example(Example const&) {}
};

int main() {
  Example e;
  Example e1(e); // Okay, direct initialization 
  Example e2 = e1; // Error! Copy initialization doesn't make use of explicit constructor
}

即使我们有一个复制构造函数,也不能在复制初始化上下文中调用它!


就程序的意外打印而言,这只是选择更匹配函数的重载解析问题。您的原始对象未声明为常量。因此,将其绑定到非常量左值引用只是重载决议中的首选。