未调用模板 class 复制构造函数

Template class copy constructor not called

我的复制构造函数没有被调用,我不确定为什么。这是我的代码:

template <typename T>
class SmartPtr
{
    public:
        explicit SmartPtr(T *p) : m_p(p) { cout << "ctor" << endl; }
        SmartPtr(const SmartPtr& p) : m_p(p.m_p) { cout << "copy ctor" << endl;}

    private:
        T* m_p;
};

int main()
{
    SmartPtr<int> pt4 = SmartPtr<int>(new int);
}

输出只有"ctor"。看起来使用了默认的复制构造函数。如果我添加 "explicit" 然后它不会编译,给出错误:

"error: no matching function for call to ‘SmartPtr<int>::SmartPtr(SmartPtr<int>)’"

我做错了什么?

这就是所谓的Copy Elision。这是一个很好的优化,显然不需要副本。而不是有效地 运行ning 代码:

SmartPtr<int> __tmp(new int);
SmartPtr<int> ptr4(__tmp);
__tmp.~SmartPtr<int>();

编译器可以知道 __tmp 只存在于构造 ptr4,因此允许在 ptr4 拥有的内存中就地构造 __tmp 好像原来的实际代码运行只是:

SmartPtr<int> ptr4(new int);

请注意,您也可以告诉编译器不要这样做。例如,在 gcc 上,您可以传递 -fno-elide-constructors 选项并通过该单一更改(另外记录析构函数),现在您的代码打印:

ctor
copy ctor // not elided!
dtor      
dtor      // extra SmartPtr!

参见 demo

在标准中,§12.8:

This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • In a return statement in a function with a class return type, when ...
  • In a throw-expression, when ...
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • when the exception-declaration of an exception handler (Clause 15) ...

[Example:

class Thing {
public:
    Thing();
    ~Thing();
    Thing(const Thing&);
};

Thing f() {
    Thing t;
    return t;
}

Thing t2 = f();

Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing: the copying of the local automatic object t into the temporary object for the return value of function f() and the copying of that temporary object into object t2. Effectively, the construction of the local object t can be viewed as directly initializing the global object t2, and that object’s destruction will occur at program exit. Adding a move constructor to Thing has the same effect, but it is the move construction from the temporary object to t2 that is elided. —end example ]