为什么谷歌不希望你使用C++构造函数


/**
* A JavaScript value representing a signed integer.
*/
class V8_EXPORT Integer : public Number {
public:
static Local<Integer> New(Isolate* isolate, int32_t value);
static Local<Integer> NewFromUnsigned(Isolate* isolate, uint32_t value);
int64_t Value() const;
V8_INLINE static Integer* Cast(v8::Value* obj);
private:
Integer();
static void CheckCast(v8::Value* obj);
};

上面的代码来自谷歌的V8引擎。初始化示例为:

Handle<Value> x = Integer::New(42);

从我在源代码中看到的情况来看,他们将构造函数标记为private,并希望您使用New函数来创建此类的实例。这不是违反了标准的C++设计模式吗?为什么他们不重载构造函数,而不是创建静态函数?当人们试图将库从一种语言移植到另一种语言时,你通常会看到这种情况(我现在唯一能想到的是Xamarin的iOS工具包)。

我试着在谷歌上搜索这种惯例的名字,但真的找不到任何名字。

这是一个名为"静态工厂方法"的模式,Joshua Bloch建议将其作为"有效Java"中的第1项。(我几乎可以肯定Scott Myers在"有效C++"中有一个等效的条目,但现在我没有这本书的副本可以查看。)

Bloch将通过这种方法而不是普通构造函数创建对象的优点描述为:

  • 这样的方法可能有一个描述性的名称
  • 与构造函数不同,创建一个全新的对象不需要这样的方法,即它们可以返回以前缓存的对象
  • 与构造函数不同,此类方法还可以返回其返回类型的任何子类型的对象
  • 这样的方法减少了参数化对象构造的冗长性

这种设计模式也有缺点,它只是在某些情况下的建议。

也许,在V8的情况下,为了加快构建速度,列表中的第二点是最重要的。我不是V8的专家,但"事件驱动,单线程"似乎是它的哲学。当许多"事件回调"想要有一个相同的编号时,它们都会获得该编号的同一实例的副本。

有两种类型的句柄。其中一个是"本地"句柄。如代码中所示,本地句柄具有类Handle<SomeType>

https://developers.google.com/v8/embed

注意:句柄堆栈不是C++调用堆栈的一部分,而是句柄作用域嵌入在C++堆栈中。句柄作用域只能是堆栈已分配,未与new一起分配。

https://developers.google.com/v8/get_started

  • 句柄是指向对象的指针。所有V8对象都是使用句柄访问的,它们是必要的,因为V8垃圾的方式收藏家的作品

用工厂方法从低级C++构造函数中抽象出来的最重要的原因是需要在这个API中结合分配和构造。大多数工厂方法执行分配。但是,这种分配必须发生在(垃圾收集的)JavaScript堆上,而不是C++堆上。这有几个后果:

  1. 我们不能允许在没有分配的情况下构造原始对象,例如在堆栈上。

  2. 我们不能允许使用C++端的new

  3. 默认情况下,我们不允许使用原始指针,因为这会破坏垃圾收集(句柄是GC知道的间接操作,可以更新以进行重新定位)。

Factory方法有助于强制执行这些限制。

看起来像是在使用静态工厂方法。当您希望集中创建对象时,这是有意义的,因为它必须以特殊的方式完成。我可以想象构造函数提供了一个简单有效的整数对象,而工厂方法则调用其他方法将对象带入一个特殊的初始状态。

将构造函数限制为尽可能小也是一个好主意。构造函数应该建立类的不变量。然后可以通过特殊的方法进行额外的设置,并且可以在工厂中封装专门初始化的对象的创建。

最新更新