【问题标题】:Copy constructor called everytime a default constructor is called?每次调用默认构造函数时都会调用复制构造函数?
【发布时间】:2015-02-16 02:56:10
【问题描述】:

当我碰到这个时,我正在处理一些关于构造函数初始化列表的问题。

考虑一下:

class Student {
    public:
        Student() {
            id = 0;
        }
        Student(int i) {
            id = i;
        }
    private:
        int id;
};

现在,看看这个:

当你进入构造函数的主体时,所有的字段都已经构造好了;如果他们有默认构造函数,那些已经被调用了。现在,如果你在构造函数的主体中为它们赋值,你就是在调用复制构造函数。这是低效的,因为最终调用了两个构造函数而不是一个。

来源:What does a colon following a C++ constructor name do?

那么,是不是说当我调用无参数构造函数的时候,拷贝构造函数也被调用了呢?

请解释一下。这真是令人困惑。

特别是第一行的意思:

当你进入构造函数的主体时,所有的字段都已经构造好了

【问题讨论】:

  • “那么,是不是说我在调用无参构造函数的时候,拷贝构造函数也被调用了?” 不是,引用状态在哪里呢?
  • 这不是一个很好的例子,因为int 是一个 POD,因此不是“默认构造的”。
  • 请解释那里的意图。什么时候调用两个构造函数而不是一个。我真的很困惑。
  • 它们指的是成员的复制构造函数(如果它们是类类型而不是int)。但这实际上是一个错误,因为调用的是赋值运算符而不是复制构造函数。
  • 主要问题是您引用的答案不太正确。首先,将“所有字段”替换为“所有成员变量”。其次,“如果你在构造函数的主体中为它们赋值,你就是在调用复制构造函数”是不正确的。您实际上是在调用默认构造函数之后调用赋值运算符。没有在同一个对象上调用 2 个构造函数的情况。除非一个构造函数显式转发给另一个构造函数,否则这是没有意义的。

标签: c++


【解决方案1】:

这意味着在到达id = 0 之前,int id 已经被初始化。因为没有指定显式初始化器,所以它已被默认初始化。另外,因为它是一个int,初始化规则告诉用户它会有一些不确定的值。

实际上,对于 int 或任何初始化成本极低的类型,这并不重要。

如果我们不使用 int 成员,而是使用类,我们可以更清楚地看到幕后实际发生的事情:

#include <iostream>

class Verbose {
    public:
        Verbose() {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }

        Verbose(int) {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }

        Verbose(Verbose const &) {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }

        Verbose & operator=(Verbose const &) {
            std::cout << __PRETTY_FUNCTION__ << "\n";
            return *this;
        }

        ~Verbose() {
            std::cout << __PRETTY_FUNCTION__ << "\n";
        }
};

class Object {
    public:
        Verbose v;

        Object() {
            v = Verbose(3);
        }
};

int main() {
    Object o;
}

这段代码将输出:

Verbose::Verbose()
Verbose::Verbose(int)
Verbose &Verbose::operator=(const Verbose &)
Verbose::~Verbose()
Verbose::~Verbose()

请注意,我们:

  1. 我们使用默认构造函数来创建v
  2. 我们创建一个临时的Verbose(3)
  3. 然后我们使用赋值运算符将临时值分配给成员变量。
  4. 然后我们销毁临时文件。
  5. Object o 超出范围时,我们会销毁成员变量。

请注意,我们基本上构造了两次Verbose v!我们首先使用了默认构造函数,然后我们基本上使用operator= 调用对其进行了重建。如果我们使用initializer list,我们可以将其减少到对Verbose(int) 的一次调用。

【讨论】:

  • 这是 OP 中的一个坏例子,因为 int id 由于它是 POD 类型而尚未构造。
  • 是的,当我们声明一个类的实例时,内存中的一些空间是为成员变量保留的,在声明过程中它要么有垃圾值,要么有一些指定的值。
  • 在所述代码中,直到赋值语句才被初始化。
  • @JimBuck:您对“默认初始化”的定义与官方 C++ 标准不同。
  • 好吧,在 C++03 中也存在这样的规则(稍微解释一下)“默认初始化非类非数组类型的对象意味着不执行初始化”。比尔引用的内容与动态分配有关,因此它不适用。
【解决方案2】:

他们是这个意思

Student() {
    id = 0;
}

效率低于

Student() : id(0) {}

在这个特定的示例中,id 将在一个步骤中初始化,因为该成员只是一个 int

相比之下,如果Student 有更复杂的成员,比如BackpackBooks,后一种方法会有所不同。如果这些类不是 POD,第一种方法需要两步来初始化成员,而第二种方法只需要一步来初始化。

【讨论】:

  • 好吧,在这种特殊情况下,这并不重要,因为 int 是一个原语。
  • 是的,成功了。 int 不会自动得到 0。
  • 是的,这个例子并不是最好的说明问题。我在最后加了一句试图解决这个问题。
  • @TheVigilante:在第二个版本中,垃圾值从未存在过。
  • 我同意@JimBuck 的观点(尽管术语令人困惑):分配 0,在分配之前没有任何事情发生(因为 int 的默认初始化什么都不做),是一步,而不是两步。我会更进一步说,如果您的编译器使Student() { id = 0; } 的效率低于Student() : id(0) { },那么您的编译器确实非常糟糕。您需要一些具有非平凡构造函数的类类型来进行默认初始化以实际执行任何操作。
猜你喜欢
  • 2017-07-28
  • 1970-01-01
  • 2016-03-25
  • 2013-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多