【问题标题】:Error spotted in C++ Primer 5th Edition (copy intialization vs direct initialization)C++ Primer 第 5 版中发现的错误(复制初始化与直接初始化)
【发布时间】:2021-03-11 05:47:18
【问题描述】:

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

{//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. 为什么写“复制构造函数将*p复制到item”?我的意思是,item 是直接初始化的。如果我们写Sales_data item = *p;,那么它将被称为复制初始化,那么为什么他们写复制构造函数将*p复制到注释中的项目中。

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

#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;
}

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

这是默认初始化

这是直接初始化

这是直接初始化

我对这个程序的问题如下:

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

【问题讨论】:

  • 请显示Sales_data 定义。它有Sales_data(Sales_data&amp;) 构造函数吗?
  • @S.M. Sales_data 具有以下构造函数:Sales_data(const Sales_data&amp;); ,Sales_data() = default;,Sales_data(const std::string &amp;s, unsigned n, double p);,explicit Sales_data(const std::string &amp;s);,explicit Sales_data(std::istream&amp;);
  • 请在问题中添加所有相关详细信息(例如Sales_data 的定义),而不是作为评论。

标签: c++ c++11 c++14 copy-constructor


【解决方案1】:

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

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

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);
}

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

复制构造函数可以用于上述任何一种情况。无论是复制初始化还是直接初始化。两者之间的区别 - 属于构造函数 - 是如何构建构造函数的重载集。复制初始化不使用显式声明的构造函数。例如,在这个例子中

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
}

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


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

【讨论】:

  • 好吧,这更有意义。谢谢。
  • 当我们编写Example obj; 时,默认构造函数是否以*const this 作为参数调用?我的意思是我们只有一个用于 const 和 nonconst 对象的默认构造函数?例如我可以写const Example a;Example b;。在这些情况下发生了什么?既然我们只有一个默认构造函数,那么这种情况是如何处理的呢? object(&a) 的地址是否作为参数隐式传递?就像普通的成员函数一样?
  • @JasonLiam - 这与您的帖子主题相去甚远。可以这么说,在构造函数中,this 永远不会指向 const 对象。构造函数总是认为对象是可修改的。常量只有在对象完全初始化后才会生效。
  • 是的,我就是这么想的,只有当我们已经有了一个对象时,constness 才会发挥作用。但是我在哪里可以阅读更多关于我上一个问题的信息?
  • @JasonLiam - 我不确定我是否完全理解你的最后一个问题。当前对象的地址总是通过this 潜入成员函数中。编译器会处理它。
【解决方案2】:

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

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

copy constructor 是一个特殊的类方法,它接受所述类的参数。这个方法应该只接受一个参数(T&amp;const T&amp; 都可以)。调用该函数时会发生复制构造

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

  1. 为什么写“复制构造函数将*p复制到item”?我的意思是,item 是直接初始化的。如果我们写成Sales_data item = *p;,那么它将被称为复制初始化...

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

  1. 为什么在我写MAINCLASS objecttwo =objectone; 的第二种情况下我们没有得到输出“这是复制初始化”?

实际上,这里的问题不在于是否复制/直接初始化。这是左值/右值重载解析的问题。

考虑以下程序:

#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&amp;)(请参阅this)。在第二种情况下,i 是一个左值,选择f(int&amp;) 是因为它更通用。

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

【讨论】:

  • 好的,我已经阅读了官方文档和您的解释(很好,基本相同),我正在总结我的理解。你能说这是否正确吗?所以,首先T obj2 = obj1;T obj3(obj1); 都使用复制构造函数。但T obj2=obj1 使用复制初始化,T obj3(obj2); 使用直接初始化。现在下一步是编译器将尝试为每种情况找到最佳匹配。如果找到匹配,那么它将使用该构造函数,这就是在我们的情况下发生的情况。对吗?
  • 是的,差不多。同样,重载解决方案(即选择调用哪个函数)是您第二个问题的主要关注点,而且它有点复杂。但我认为,你正在阅读的这本书最终会解决这个问题。
  • 好的,所以 Sales_data 类具有以下构造函数:Sales_data(const Sales_data&amp;);,Sales_data() = default;,Sales_data(const std::string &amp;s, unsigned n, double p);,explicit Sales_data(const std::string &amp;s);,explicit Sales_data(std::istream&amp;); 如您所见,那里有一个复制构造函数,它将当我们写Sales_data item=*p 时被调用。现在,如果我再添加一个构造函数说Sales_data(Sales_data&amp;);,那么我们不能再在注释中写“使用了复制构造函数”,因为这次它不会被使用?对吗?
  • Sales_data(Sales_data&amp;); 仍然是一个复制构造函数(尽管具有不同的签名)。 Sales_data item = *p 会这样称呼。所以评论仍然适用。
  • 复制赋值运算符=.和拷贝构造函数类似,Sales_data operator=(Sales_data&amp;);Sales_data operator=(const Sales_data&amp;);都是拷贝赋值运算符吗?
【解决方案3】:

复制初始化和直接初始化是基于用于构造的语法。
Confusion in copy initialization and direct initialization

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

在您的示例中,由于objectone 是非常量,因此最佳匹配是具有非常量参数的复制构造函数。由于另一个复制构造函数有一个const&amp; 参数,它会被一个 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

【讨论】:

  • 但我读到“复制初始化发生在我们使用 = 定义变量(对象)时。那么为什么在您的示例中,即使您直接初始化它也会调用复制构造函数。我认为编译器可以随意绕过这个复制构造函数初始化。这就是为什么在我的自定义示例中它正在这样做。根据您的示例,由于除了复制构造函数之外,它们没有以 const MAINCLASS& 作为参数的构造函数,这就是为什么当编译器找不到任何匹配的构造函数时,它会尝试复制构造函数。对吗?
  • 请参阅stackoverflow.com/questions/1051379/… 了解复制初始化和直接初始化的说明。
  • 默认构造函数有两个版本吗?一个MAINCLASS(); 和另一个MAINCLASS() const;。首先用于非常量对象,其他用于 const 对象?
  • const 对象没有特殊的构造函数。 const 是一个对象限定符,表示对象(创建后)不能被修改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-08
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
相关资源
最近更新 更多