8

如何禁止构建对象?我将= 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仍然可以存在。该怎么办?我对这两种情况都感兴趣;静态/堆栈分配和堆分配。

4

5 回答 5

14

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

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

[现场示例]

于 2015-10-15T10:39:10.133 回答
6
  1. 如果您只想拥有static成员,请编写namespace A而不是struct A. 随后的代码在语法上将是相似的。

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

于 2015-10-15T10:37:52.483 回答
6

如果你想让类实例化变得不可能,你可以声明私有构造函数:

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.
    }
};
于 2015-10-15T10:39:27.357 回答
3

通常,为了完全防止类的客户端代码实例化,您可以声明该类final,并且

  • 使构造函数非public, 或

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

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

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


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

  • 使析构函数非public.

这可以防止自动和静态变量,但不会防止使用new.

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

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

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


代码使用至少一个版本的 clang 编译器编译的事实-std=gnu++1z是由于该编译器中的错误和/或语言扩展。

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

代码使用至少一个版本的 clang 编译器编译的事实-std=gnu++1z可能是由于该编译器中的错误和/或语言扩展。正确的行为是什么,尚不清楚,因为

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

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

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

C++11 §8.4.2/4

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

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

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

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

于 2015-10-15T11:11:58.080 回答
1

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

什么是聚合类型?;

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

  • 没有私人或受保护的成员
  • 没有用户提供的构造函数(允许显式默认或删除的构造函数)(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默认构造函数上的 a 能够有效地禁止类创建(在其他用户定义的构造函数之外)。


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

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

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

struct A
{
private:
    A(); // note no =delete...
};
于 2015-10-15T12:27:19.860 回答