在 c++ 中,如果首先禁止默认构造,那么禁止复制构造是否有意义?

In c++, Does it make sense to prohibit copy construction if the default construction is prohibited in the first place?

提问人:arnie7 提问时间:11/1/2019 最后编辑:arnie7 更新时间:11/1/2019 访问量:195

问:

我正在经历一个代码实现,其目的是不让任何人创建特定类的对象。下面是代码片段:

class CantInstantiate
{
    CantInstantiate();
    CantInstantiate(const CantInstantiate&);
    ...
};

如果默认构造函数已经设置为私有未定义(前提是没有其他构造函数),是否真的需要将复制构造函数 private undefined ?当我们一开始就没有原始对象时,防止复制对象有什么好处?请解释。提前致谢。

C++ 复制构造函数 default-constructor

评论

4赞 Jesper Juhl 11/1/2019
最好是复制 ctor,而不是旧的“私有和未实现”的 hack。= delete;
2赞 Peter - Reinstate Monica 11/1/2019
哦,我意识到您的默认构造函数不仅是私有的,而且是未定义的,并且没有其他 ctor。您的类将只有静态成员,并且基本上用作命名空间。在这种情况下,不能有任何对象,我看不出有什么理由(除了在代码中记录意图,这是一件好事)删除复制 ctor。

答:

0赞 Chris B 11/1/2019 #1

如果目的是防止创建类的实例,则需要确保不能调用任何构造函数。如果你没有明确禁止复制构造,那么你将由编译器来决定是否包含一个,这取决于导致删除隐式声明的复制构造函数的条件,这些条件是:

- T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors);
- T has direct or virtual base class that cannot be copied (has deleted, inaccessible, or ambiguous copy constructors);
- T has direct or virtual base class with a deleted or inaccessible destructor; 
- T is a union-like class and has a variant member with non-trivial copy constructor;
- T has a data member of rvalue reference type;
- T has a user-defined move constructor or move assignment operator (this condition only causes the implicitly-declared, not the defaulted, copy constructor to be deleted).

因此,如果我们假设你有一个类 A,它试图通过仅禁止默认构造而不包含上面列表中的任何内容来防止自身被构造,那么就有可能利用隐式复制构造函数来获取 A 的实例,甚至从它继承。例如:

class A
{
    A(){};
};

struct B : public A {
    B() : A(*a)
    {}
    A* a;
};

B b;
A a (*static_cast<A*>(nullptr));

现在,诚然,上面的代码可能会产生意想不到的行为,如果您尝试这样做,任何像样的编译器都会产生警告,但它确实会编译。这种方法的实用性将完全取决于在A...

我认为,如果这个想法是停止创造,那么你需要确保所有的建筑方法都被阻止。因此,显式删除默认构造函数、复制构造函数和移动构造函数是一种更好的方法。这发出了更明确的意图声明,我相信它应该阻止所有施工方法。

class CantInstantiate
{
public:
    CantInstantiate() = delete;
    CantInstantiate(const CantInstantiate&) = delete;
    CantInstantiate(CantInstantiate&&) = delete;
    ...
};

评论

0赞 Peter - Reinstate Monica 11/1/2019
虽然显式删除它们可能更清晰、更易于维护,但我认为没有必要。你的例子是无效的——当然,你可以把任何东西投射到任何东西上,什么也证明不了。永远不会有 ,因此你永远无法调用一个 copy,无论是否删除。A
0赞 Peter - Reinstate Monica 11/1/2019
然后,您可能应该删除移动构造函数和赋值运算符(您的左侧也可能是假引用,而您正在使用它!也许我们还应该删除析构函数(这可能吗?只是为了确定;-)。
0赞 Chris B 11/1/2019
我不同意你的第一条评论。强制转换的目的是生成复制构造函数可接受的输入。这意味着在 A 上调用了一个构造函数,因此我们有一个 A 的实例。我接受将 nullptr 转换为引用是未定义的行为。我只是想证明,如果你不明确阻止它,那么它可能会被利用。
0赞 Chris B 11/1/2019
我同意应该添加移动构造函数,我编辑了帖子。 应通过声明移动构造函数来防止隐式复制赋值。通过定义构造函数来防止隐式移动赋值。也可以删除析构函数,但我认为它不会添加任何东西,因为如果您无法构建它,那么就没有什么可以阻止删除的。