私有的new运算符会有任何意外的副作用吗?

4
我在这篇博客读到,将new运算符设为私有是一种强制在堆栈上实例化的好方法。
我正在实现一个采用RAII惯用语法的类。显然,这个类只能在堆栈上实例化,因此我正在寻找一种方法来强制执行。
我的问题是,这样做是否会有任何不容易看出的副作用?
这是否是在堆栈上实例化的好方法?
是否存在任何可移植性问题?
谢谢你的帮助!
编辑:
我的RAII类只是实例化我正在工作的框架的各个部分,因此对于该类没有其他可行的操作,除了在堆栈上创建一个实例。
目标只是提供一种简单的可能性来配置框架并将其置于可用状态,而无需在客户端代码中实例化10个对象。
3个回答

7

这个类显然只能在堆栈上实例化,所以我正在寻找一种强制执行的方法。

如果你必须强制执行,那么对用户来说显然并不明显...

主要的缺点是它无法工作

即使新的操作符是私有的,用户仍然可以意外地将您的类用作其自己类的数据成员或基类,然后使用new实例化他们的类。这会阻止他们编写Foo *f = new Foo();,而应该编写Foo f;,但它并不能强制执行他们对RAII类的使用与词法范围匹配,这可能是您真正想要强制执行的内容。

如果有人想要使用具有动态存储期的锁(或其他东西),那么他们要么非常聪明,要么非常愚蠢。无论哪种情况,如果您让他们自己创建,则无法阻止他们绕过您的限制。

您可以将所有构造函数设置为私有(包括复制构造函数),然后提供一个友元函数按值返回。他们必须编写:

const Foo &f = GimmeAFoo();

标准要求扩展返回值的生命周期,只要f的范围。由于它们无法复制对象,因此无法使值逃离引用的范围。由于它们无法初始化数据成员或基类,因此无法使用解决方法。请注意,它们必须取一个const引用,所以如果您的类需要非const成员,则实际上不起作用。

他们仍然可以做一些愚蠢的事情,比如像这样初始化自己类的const引用数据成员,但是对象本身不会逃离发生这种情况的范围。他们将得到一个悬空引用,就像他们如果获取了指向不应该的东西的指针一样。


3
不要试图阻止完全的愚蠢,因为你无法做到。相反,对你的班级用户进行教育,告诉他们如何正确使用它。 - Fred Nurk
感谢您提供这个详尽的解决方案,我没有看到这两种解决方法。虽然我必须说,我更喜欢实用主义的方法,只覆盖最常见的错误,正如Christian Rau所指出的那样。 - theDmi

2
即使您无法防止其他人将RAII类作为基类或动态创建对象的成员,正如Steve所说,您应该始终告知用户您对类的预期使用,但将new运算符设置为私有以防止最明显的误用仍然是一个好主意。
由于这是有效的C ++代码,因此不存在可移植性问题。

2
将operator new设置为私有并不是一个好主意。这会阻止一些有效的使用,例如boost::optional或(使用移动语义)像vector这样的容器。 - Fred Nurk
啊,我明白你的意思了。我原本认为向量是个坏主意,但是有了新的移动语义,它或许可以有些意义。 - Christian Rau

2
我正在实现一个使用RAII习惯用法的类。显然,这个类只能在堆栈上实例化,因此我正在寻找一种方法来强制执行它。
那不是RAII。 RAII是在构造时初始化并在析构函数中取消初始化。 RAII是关于您控制的资源(例如数据成员),而不是“父”对象的生命周期。 然后,您的类型的用户再次应用RAII以控制他们分配的内容的生命周期。 例如,您可以动态分配std :: vector,即使vector使用RAII确保每个它“拥有”的项目都被清理。
我的问题是,这是否会产生任何不容易看到的副作用?
是的,您会阻止否则有效(至少就RAII而言)使用您的类型。
void example() {
  shared_ptr<T> p (new T());
  // No RAII violation, still uses operator new.

  vector<T> v (some_initial_size);
  // No RAII violation, still uses operator new (the placement form is used
  // inside the default allocator).
}

在栈上实例化是一个好的方法吗?

你想要防止什么?使用场景是什么?任何使用new操作符创建你的类型的人,要么1)知道他们在做什么并且需要它,要么2)无论你怎么做都会糟糕地管理资源。试图强制执行这个规则只会妨碍那些在#1中的人,而不会帮助那些在#2中的人。

这是另一种表述Steve所说的话:

这个类显然只能在栈上实例化,所以我正在寻找一种强制执行这种规则的方法。

如果这是显而易见的,为什么还需要强制执行?

如果用户会盲目使用 "new T()",那么你认为他们不会盲目使用 "::new T()" 吗?


目标只是提供一个简单的方式来配置框架,并将其置于可用状态,而不必在客户端代码中实例化10个对象。

#include <framework>

int main() {
  framework::Init init;
  // Do stuff.
}

在你的文档示例中,要突出使用这个或类似的内容。

我正在编写一个简单的类,该类在构造函数中实例化框架的所有必需部件,并在析构函数中清理它们。就我所知,这是RAII。 但是我的问题与RAII无关,我只是想为问题提供一些背景。 - theDmi
1
@Daniel M:这与在堆栈或堆上分配有何关系?如果我希望您的框架寿命基于堆,该怎么办? - Puppy
我按照你的建议,移除了类中所有的“使用正确”的强制规定。毕竟我们都是工程师,可以期望一些最基本的智力...感谢你在这个话题上的贡献。 - theDmi
1
@Daniel M:显然你没有理解我的观点。如果我正在使用你的框架,那么如果我想将其生命周期绑定到堆栈或堆上,那就是我的选择。RAII只是让我按照自己的意愿进行绑定的工具。然而,你绝对不应该告诉我我不能将它绑定到我选择放置在堆上的对象上。我的观点是,一个自清理类与如何选择对象的生命周期完全无关。我的评论并不是在炫耀。 - Puppy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接