【问题标题】:Exception and Copy Constructor : C++异常和复制构造函数:C++
【发布时间】:2013-05-02 05:28:19
【问题描述】:

参考http://en.wikipedia.org/wiki/Copy_elision

我运行以下代码:

#include <iostream>

struct C {
  C() {}
  C(const C&) { std::cout << "Hello World!\n"; }
};

void f() {
  C c;
  throw c; // copying the named object c into the exception object.
}          // It is unclear whether this copy may be elided.

int main() {
  try {
    f();
  }
  catch(C c) {  // copying the exception object into the temporary in the exception declaration.
  }             // It is also unclear whether this copy may be elided.
}

我得到的输出:

Gaurav@Gaurav-PC /cygdrive/d/Trial
$ make clean
rm -f Trial.exe Trial.o

Gaurav@Gaurav-PC /cygdrive/d/Trial
$ make
g++ -Wall Trial.cpp -o Trial

Gaurav@Gaurav-PC /cygdrive/d/Trial
$ ./Trial
Hello World!
Hello World!

我知道编译器可能已经通过不必要的复制优化了代码,但这里没有这样做。

但我想问的是,two calls to the copy constructor 是如何制作的?

catch(C c) - 因为我们是按值传递的,所以这里调用了复制构造函数。

但是在throw c 是如何调用复制构造函数的?谁能解释一下?

【问题讨论】:

    标签: c++


    【解决方案1】:

    但是在 throw c 是如何调用复制构造函数的呢?谁能解释一下?

    如果你想做throw ex;,C++ 异常必须是可复制/移动可构造的,因为幕后发生的事情是 C++ ABI 将分配一个异常对象(通过 __cxa_allocate_exception)somewhere 并复制/在实际开始堆栈展开过程之前移动您的异常对象,无论它是在堆上还是在堆栈上。

    参考https://blog.the-pans.com/cpp-exception-2/

    【讨论】:

      【解决方案2】:

      在抛出用户定义的类型对象时复制和移动构造函数

      struct demo
      {
          demo() = default;
          demo(demo &&) = delete;
          demo(const demo &) = delete;
      };
      
      int main()
      {
          throw demo{};
          return 0;
      }
      
      • 在抛出表达式时,始终需要创建异常对象的副本,因为原始对象在堆栈展开过程中超出范围。
      • 在初始化期间,我们可能会期望复制省略(参见此内容)——省略复制或移动构造函数(对象直接构造到目标对象的存储中)。
      • 但是,即使可能应用或不应用复制省略,您也应该提供正确的复制构造函数和/或移动构造函数,这是 C++ 标准要求的(参见 15.1)。请参阅下面的编译错误以供参考。
      error: call to deleted constructor of 'demo'
          throw demo{};
                ^~~~~~
      note: 'demo' has been explicitly marked deleted here
          demo(demo &&) = delete;
          ^
      1 error generated.
      compiler exit status 1
      
      • 如果我们通过值捕获异常,我们也可能期望复制省略(编译器允许这样做,但不是强制性的)。初始化 catch 子句参数时,异常对象是一个左值参数。

      发件人:7 best practices for exception handling in C++

      【讨论】:

        【解决方案3】:
        throw c;     
        

        创建一个临时对象,并抛出这个临时对象。临时的创建可能是通过复制/移动构造函数。是的,这个复制/移动可以省略。


        参考:
        C++11 15.1 抛出异常

        §3:

        throw-expression 初始化一个临时对象,称为异常对象,其类型是通过从 throw 操作数的静态类型中删除任何顶级 cv 限定符并调整来确定的类型…………

        §5:

        当抛出的对象是类对象时,复制/移动构造函数和析构函数应该是可访问的,即使复制/移动操作被省略(12.8)。

        【讨论】:

        • Alok - 你能给我一个移动构造函数的例子吗?它与移动分配运算符相同吗?能否请您回复EDIT 部分是stackoverflow.com/questions/16418992/… 的问题
        • @BenVoigt,cmets 似乎比问题更有信心,这可能是因为他们直接来自维基百科。
        • 那里,“初始化一个临时对象”是缺少的。
        • @GauravK:除了 C++03 中已经存在的复制语义外,C++11 还引入了移动语义。 This 问题可能是一个很好的起点。
        • @GauravK:是的,在 C++11 中引入。通常,您不应该或不能依赖计数复制/移动构造函数调用。一个好的编译器会尽可能通过 RVO 或 NRVo 应用复制省略。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-14
        • 1970-01-01
        • 2023-03-20
        相关资源
        最近更新 更多