【问题标题】:How to prohibit the construction of object?如何禁止构造对象?
【发布时间】:2016-01-13 18:24:57
【问题描述】:

如何禁止对象的构造?我将= delete;所有相关特殊功能标记如下:

struct A
{
    A() = delete;
    A(A const &) = delete;
    A(A &&) = delete;
    void * operator new(std::size_t) = delete;
    void operator delete(void *) = delete;
};
A x{};
A y = {};
A * z = ::new A{};

LIVE EXAMPLE

xy*z 仍然可以存在。该怎么办?我对这两种情况都感兴趣;静态/堆栈分配和堆分配。

【问题讨论】:

  • 不清楚您要做什么。 IFAIK 没有标准的符合标准的方法来创建对象而不构造它(对于非 POD 类型)。
  • @GregorMcGregor 你错了coliru.stacked-crooked.com/a/b43ee3e03590be79
  • 此代码不是有效的 C++。如果 clang 编译它,那是 clang 的问题,而不是 C++ 的问题。提交一个针对 clang 的错误。 Gcc rejects the code as it should.
  • @n.m.实际上,我认为这是一个 GCC 错误。这三种形式都应该是聚合初始化,不调用默认构造函数。
  • @TartanLlama 哎哟你是对的。它是一个聚合体。我的错!

标签: c++ c++11 memory-management c++14 memory-layout


【解决方案1】:

一种选择是给类一个纯虚函数,并将其标记为final:

struct A final
{
  virtual void nonconstructible() = 0;
};

[Live example]

【讨论】:

  • 你也可以使用纯虚析构函数使类抽象而不是纯虚函数。
  • @Bathsheba 这就是我发布答案的原因(除了支持你的)。
  • 有人能解释一下你不能实例化的最终虚拟类的目的是什么吗?
  • @Hassan OP 要求提供一个您无法构建的类。询问他们的动机。
  • @Hassan 我能想到的唯一一件事是 OP 在评论中提到的 - 有一个只有静态成员的 X,但能够将 X 作为模板参数传递(这排除了使用命名空间为X)。
【解决方案2】:
  1. 如果您只想拥有static 成员,请写namespace A 而不是struct A。随后的代码在语法上将相似。

  2. 要防止创建类的实例,请将其设为抽象。 (包括一个纯虚函数)。但是这样做会在你的类中引入一个 v-table,这可能是你不想要的。

【讨论】:

  • 第一个解决方案的缺点是您不能将其用作模板参数。第二种解决方案的缺点是可以通过继承来实例化。
  • 但不能直接实例化。 @Angew 对此有一个解决方案,我不愿意抄袭。
【解决方案3】:

如果你想让类无法实例化,你可以声明私有构造函数:

class NotInstantiable {
private:
    NotInstatiable();

public:
};

并没有进一步定义NotInstantiable。现在无法实例化,因为首先构造函数是 private,而且还没有提供构造函数的定义。

实例化NotInstantiable 的第二个障碍是例如禁止这种可能性,否则这实际上是众所周知的模式:

class NotInstantiable {
private:
    NotInstantiable();

public:
    NotInstantiable* evil_method()
    {
        return new NotInstantiable(); // this will fail if there's no body of the constructor.
    }
};

【讨论】:

  • 即使默认的 c-tor 仍然定义在类的私有部分中,您也保持正确。
【解决方案4】:

一般来说,要完全防止类的客户端代码实例化,您可以声明类 final 或者

  • 使构造函数不是public,或者

  • 删除构造函数并确保该类不是聚合类,或者

  • 添加一个纯虚成员函数(例如,使析构函数纯虚)以使类抽象。

当非publicprotected 时,必须声明类final,对于抽象类,以防止实例化派生类的基类子对象。


要部分禁止实例化,您可以

  • 使析构函数不是public

这会阻止自动和静态变量,但不会阻止使用new 进行动态分配。

  • 使类的分配函数(operator new)非public

这可以防止通过客户端代码中的普通new-表达式进行动态分配,但它不提供自动和静态变量,或其他对象的子对象,也不会防止通过::new- 进行动态分配表达式,使用全局分配函数。

还有其他相关技术,例如带有额外参数的分配函数,这使得new-表达式异常复杂和不切实际。我曾经用它来强制使用特殊宏来动态分配对象,例如对于shared-from-this class。但那是在 C++11 支持参数转发之前的时间。现在一个普通的函数就可以了,这样的函数可以做成类的friend


代码compiles with at least one version of the clang compiler-std=gnu++1z 是由于该编译器中的错误和/或语言扩展造成的。

代码不应编译,因为它调用了已删除的默认构造函数。它不能与例如编译MinGW g++ 5.1.0,即使使用-std=gnu++1z

代码compiles with at least one version of the clang compiler-std=gnu++1z 的事实可能是由于该编译器中的错误和/或语言扩展。正确的行为是什么,尚不清楚,因为

  • 虽然代码使用 clang 和 Visual C++ 2015 编译,但它不能使用例如编译。 MinGW g++ 5.1.0,即使使用-std=gnu++1z

  • 直观地说,如果代码应该编译,delete 将毫无意义,但 C++ 中允许许多无意义的构造。

  • 问题在于类是否是聚合(在这种情况下new表达式执行聚合初始化),这取决于删除的默认构造函数是否可以被视为用户提供的。正如用户TartanLlama 在 cmets 中解释的那样,user-provided 的要求是

C++11 §8.4.2/4

一个特殊的成员函数是用户提供的,如果它是用户声明的而不是显式的 在第一次声明时默认或删除。

即尽管此问题示例中默认构造函数的 delete 声明了该构造函数,但它不是用户提供的(其他成员也是如此),因此该类是一个聚合类。

我能找到的关于这个措辞的唯一缺陷报告是DR 1355,但它只是涉及使用“特殊成员”这个词的问题,并建议删除这些词。但是,考虑到这个问题所展示的效果,并考虑到一个函数只能在其第一次声明时被删除,措辞很奇怪。

正式总结一下,从 C++11 开始(我还没有检查过 C++14),代码应该可以编译。但这可能是标准中的一个缺陷,措辞不反映意图。并且由于 MinGW g++ 5.1.0 不编译代码,截至 2015 年 10 月依赖代码编译并不是一个好主意

【讨论】:

  • 这真的是病态的吗?我认为A 是一个聚合,因为它没有用户提供的构造函数或数据成员,所以这三种形式都应该是聚合初始化,不调用默认构造函数。
  • @TartanLlama:被删除后,默认构造函数已声明
  • @Alf but user-provided 被定义为“用户声明且未在其第一次声明时明确默认或删除”。
  • 相关标准条目为[dcl.fct.def.default](C++11中为/4,C++14中为/5)和[dcl.init.aggr]/1
  • @TartanLlama:哦。因此,如果您是对的,那么我们谈论的是 g++ 中的编译器错误或标准中的缺陷。我得去看看。需要一些时间,因为我必须做一些其他的事情。对不起。
【解决方案5】:

本质上这是编译和允许的,因为类型A 是一个聚合类型并且聚合初始化不使用默认构造函数。

What is an aggregate type?;

类类型(通常是结构或联合),具有

  • 没有私有或受保护的成员
  • 没有用户提供的构造函数(允许显式默认或删除的构造函数)(C++11 起)
  • 没有基类
  • 没有虚拟成员函数

给它以上任何一种都会使它成为非聚合的,因此聚合初始化将不适用。给它一个私有的用户定义(和未实现)的构造函数就可以了。

struct A
{
    A() = delete;
    A(A const &) = delete;
    A(A &&) = delete;
    void * operator new(std::size_t) = delete;
    void operator delete(void *) = delete;
private:
    A(int);
};

作为旁注;我希望这是语言规范中的缺陷。乍一看,我认为这不应该编译,但确实如此。 =delete 的动机之一是避免 C++03 将构造函数声明为私有以“隐藏”它们从而无法使用的“技巧”。我希望默认构造函数上的=delete 可以有效地禁止类创建(在其他用户定义的构造函数之外)。


为了更容易阅读和更清晰的意图,甚至可以考虑一个空的基类;

struct NonAggregate{};
struct A : private NonAggregate
{
    //...

也许最简单的还是这里回到C++03风格,将默认构造函数设为私有;

struct A
{
private:
    A(); // note no =delete...
};

【讨论】:

  • 拥有私有成员可防止聚合。但是任何私人成员都可以。我不会专门处理默认构造函数,这会有点误导,我只会使用一个空的基类Non_aggregate
  • @Cheersandhth.-Alf。那行得通。也许也使代码更易于阅读。不过,这是一个特殊的角落案例。
  • 你可以将删除的方法private,我认为你不需要添加额外的A(int)ctor。
  • @MSalters。删除 =delete 仍然有效(就像在 C++03 中所做的那样)。
  • 哦,这都是 Bjarne 的错。那里。我说了。 :)
猜你喜欢
  • 2014-11-18
  • 2010-09-10
  • 2018-05-10
  • 2022-01-02
  • 1970-01-01
  • 2012-05-15
  • 1970-01-01
  • 2010-12-07
  • 2015-10-11
相关资源
最近更新 更多