【问题标题】:C++ copy on member access成员访问的 C++ 副本
【发布时间】:2017-12-25 13:07:59
【问题描述】:

我正在做一些实验,看看除了复制省略、RVO、NRVO 情况之外何时执行复制。

所以我写了一些这样的代码:

class X {
 public:
  X() { std::cout << "Default constructor" << std::endl; }

  X(const X&) { std::cout << "Copy constructor" << std::endl; }

  X(X&&) { std::cout << "Move constructor" << std::endl; }

  X& operator=(const X) {
    std::cout << "Assignment operator" << std::endl;
    return *this;
  }

  X& operator=(X&&) {
    std::cout << "Move assignment operator" << std::endl;
    return *this;
  }

  ~X() { std::cout << "Destructor" << std::endl; }
};

class Y {
 private:
  X x;

 public:
  const X& getX() const {
    std::cout << "getX" << std::endl;
    return x;
  }
};

int main() {
  Y y;
  std::cout << "assign to ref" << std::endl;
  const X& x1 = y.getX();
  (void)x1;
  std::cout << "assign to const" << std::endl;
  const X x2 = y.getX();
  return 0;
}

我收到以下输出:

Default constructor
assign to ref
getX
assign to const
getX
Copy constructor
Destructor
Destructor

使用 gcc 编译或使用 -O3 进行 clang 编译并尝试 -std=c++{11,14,17} 都产生相同的输出。

令我惊讶的是,我没想到在使用 y.getX(); 时会执行任何复制;到一个 const 变量。这是我经常使用的东西,只是为了简化我在以下代码中对该变量及其成员的访问,但我并没有通过 const 引用执行它,而是我只是使用 const 希望编译器将它视为重命名。

有谁知道为什么要执行该副本?我想到的唯一原因是它使代码线程安全。如果有多个线程处理对象 y,那么我对 const 的分配毕竟不会是那个 const。因为它只会引用对象 y 中的成员 x。这可能会被其他线程更改。但我不确定这是否是真正的意图。

【问题讨论】:

  • 在 C++ 中,你得到你想要的。如果您定义一个对象并通过复制对其进行初始化,您将得到两个相同的对象,而不是现有对象的某个句柄。
  • 好吧,也许您只是忘记了&amp;?通过这种方式,您要求并获得一份副本,而不是参考资料。 const X&amp; 将再次按您期望的方式工作。
  • 实际上我期待两者的行为相同。正如你所看到的,我对它们都进行了测试,我很确定 const 参考不会复制,但不能确定为什么只是 const 也会复制。因为它应该是对y中的成员变量的只读访问。
  • 不,应该是一个新的对象,和原来的一样,正好是const。如果对象可以根据周围的代码以及优化器的计算结果隐式出现和消失并改变生命周期,那么编程将是疯狂的。
  • @Quentin “如果对象可以根据周围的代码和优化器的计算结果隐式出现和消失并改变生命周期,那么编程将是疯狂的。” 呃,RVO 确实如此正是如此。

标签: c++ copy-constructor copy-elision rvo nrvo


【解决方案1】:

要查看 RVO verses 编译器强制使用 NRVO 的效果,请在以下修改后的程序上使用-fno-elide-constructors 编译器开关。使用通常的选项:

Default constructor 1
assign to ref
getX (with id: 1)
x1 (id:1)
assign to const
getX (with id: 1)
Copy constructor 2
x2 (id:2)
make_X copy
Default constructor 3
make_X (with id: 3)
x3 (id:3)
make_X ref
Default constructor 4
make_X (with id: 4)
x4 (id:4)
Destructor 4
Destructor 3
Destructor 2
Destructor 1

但是有了 NRVO,你会得到:

Default constructor 1
assign to ref
getX (with id: 1)
x1 (id:1)
assign to const
getX (with id: 1)
Copy constructor 2
x2 (id:2)
additional 1
Default constructor 3
make_X (with id: 3)
Move constructor 4
Destructor 3
Move constructor 5
Destructor 4
x3 (id:5)
additional 2
Default constructor 6
make_X (with id: 6)
Move constructor 7
Destructor 6
x4 (id:7)
Destructor 7
Destructor 5
Destructor 2
Destructor 1

代码示例:

#include <iostream>
int global_id;
class X {
public:
    X() : id(++global_id) {
        std::cout << "Default constructor " << id << std::endl;
    }
    X(const X&) : id(++global_id) {
        std::cout << "Copy constructor " << id << std::endl;
    }
    X(X&&) : id(++global_id) {
        std::cout << "Move constructor " << id << std::endl;
    }
    X& operator=(const X&) {
        std::cout << "Assignment operator " << id << std::endl;
        return *this;
    }
    X& operator=(X&&) {
        std::cout << "Move assignment operator " << id << std::endl;
        return *this;
    }
    ~X() {
        std::cout << "Destructor " << id << std::endl;
    }
    int id;
};

class Y {
    X x;
public:
    const X& getX() const {
        std::cout << "getX (with id: " << x.id << ')' << std::endl;
        return x;
    }
    X make_X() const {
        X extra;
        std::cout << "make_X (with id: " << extra.id << ')' << std::endl;
        return extra;
    }
};

int main()
{
    Y y;
    std::cout << "assign to ref" << std::endl;
    const X& x1 = y.getX();
    std::cout << "x1 (id:" << x1.id << ")\n";
    (void) x1;
    std::cout << "assign to const" << std::endl;
    const X x2 = y.getX();
    std::cout << "x2 (id:" << x2.id << ")\n";
    std::cout << "make_X copy" << std::endl;
    const X x3 = y.make_X();
    std::cout << "x3 (id:" << x3.id << ")\n";
    std::cout << "make_X ref" << std::endl;
    const X& x4 = y.make_X();
    std::cout << "x4 (id:" << x4.id << ")\n";
    return 0;
}

如您所见,RVO 确实只与局部变量一起使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-13
    • 2014-07-29
    • 2012-10-01
    • 1970-01-01
    • 2017-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多