声明对象而不调用默认构造函数

Declare object without calling default constructor

本文关键字:默认 构造函数 调用 对象 声明      更新时间:2023-10-16

我是C++新手,习惯于使用Java。 在 Java 中,我可以选择在不实例化对象的情况下声明一个对象,并希望在 C++ 中做同样的事情。

假设有一些类Foo,在Java中我可以编写Foo bar;来声明Foo的实例而不初始化bar。 但是,在C++我写Foo bar;时,bar是通过调用类Foo的默认构造函数来初始化的。

如果我为类Foo编写了一个或多个构造函数,每个构造函数至少有一个参数,这尤其令人烦恼。在这种情况下,代码将无法编译,并显示类似于no matching function for call to 'Foo::Foo()'

例如,假设我在 Java 中有以下类定义:

public class Foo {
private boolean b;
Foo(boolean b) { this.b = b; }
}

以及 C++ 中相应的类定义:

class Foo {
bool b_;
public:
Foo(bool b) : b_(b) {}
};

在Java中,我可以编写一些方法

public static Foo makeFoo(int x) {
Foo result;
if (x > 10) { result = new Foo(true); }
else { result = new Foo(false); }
return result;
}

但是,如果我在C++中编写类似的方法,则会出现编译错误:

Foo makeFoo(int x) {
Foo result; // here, a call is made to Foo::Foo() which doesn't exist, and thus compilation fails
if (x > 10) { 
result = Foo(true); // this would probably also fail since I didn't specify a copy-constructor, but I'm not entirely sure
}
else {
result = Foo(false); // as above, I think this would probably fail
}
return result;
}

虽然我给出的例子毫无用处,但我在编写 Java 代码时经常使用这种方法。 有没有办法在C++中模拟这种行为? 或者,这只是糟糕的设计吗?如果是这样,您会推荐哪种方法?

如果您不想使用指针来获取 Igor (和其他人)在您的问题的第一条评论中解释的参考功能,那么您可以做几件事。

首先,值类型而不是引用类型的理念是,在需要它们之前不要创建它们。 在使用对象之前声明引用的诱惑是在函数的其余部分(也许是一些创建后的公共 init 代码)中获得一种多态功能,这是合理的设计,但不能用你编写它的方式表达,因为它会涉及创建一个值。

你可以提供一个默认的构造函数并给它一些行为——但很明显,你和其他人都不想被强迫这样做。

替代方法的本质是将变量移动到作用域内并返回它。

Foo makeFoo(int x) {
if (x > 10) { 
Foo result = Foo(true);
return result;
}
else {
Foo result = Foo(false);
return result;
}
}

显然,这会阻止您在返回之前在if块之后编写常见的创建后初始化代码。 为此,您需要在其自己的函数中编写if块并让它返回结果,然后在初始化对象后编写后续代码。

Foo premakeFoo(int x) {
if (x > 10) { 
Foo result = Foo(true);
return result;
}
else {
Foo result = Foo(false);
return result;
}
}
Foo makeFoo(int x) {
Foo result = premakeFoo(x);
// common post init code can go here.
return result;
}

如果你不希望它在一个完全独立的函数中,你可以做一个lambda。

Foo makeFoo(int x) {
Foo result = ([=]() {
if (x > 10) {
Foo result = Foo(true);
return result;
} else {
Foo result = Foo(false);
return result;
}
})();
// common post init code can go here.
return result;
}

或者,在您的情况下,如果它很小,请使用内联三元表达式。

Foo makeFoo(int x) {
Foo result = (x > 10) ? Foo(true) : Foo(false); // or just Foo(x>10)
// common post init code can go here.
return result;
}

还有其他涉及模板的聪明选项,但它们都围绕着隔离重载构造函数的不同表达式的想法,然后使用赋值来初始化给定一些更复杂的表达式的变量。 您正在尝试做的是获取以两种不同方式构造值以跨越范围块(跨越if块)的表达式。 三元和函数是用于在单个表达式中执行if逻辑的选项。

在 Java 中,当你创建对 Foo 的引用Foo result;时,你实际上并没有创建一个对象。 C++是不同的,因为它具有价值语义,并且Foo result;实际上创建了一个对象。 如果没有默认构造函数,则会引发错误。

要获得相同类型的行为,您需要在C++中使用指针。 您不能使用引用,因为与 Java 不同,引用在创建时需要绑定到对象。 因此,如果使用std::unique_ptr<Foo>则可以声明该std::unique_ptr<Foo>,然后稍后使用在创建逻辑中初始化的对其进行初始化。 使用您的示例,代码将如下所示

Foo makeFoo(int x) {
std::unique_ptr<Foo> result; // here result doesn't have an actual object yet
if (x > 10) { 
result = std::make_unique<Foo>(true); // now we pass the values we want to construct with and get a valid object
}
else {
result = std::make_unique<Foo>(false); // same as above
}
return *result; // this lets you return a `Foo` which will be a copy, and the Foo created by make_unique will be destroyed
}

如果不想制作副本,可以改用return result;,并更改函数以返回std::unique_ptr<Foo>

您尝试执行的操作称为工厂模式,通常以这种方式实现

std::unique_ptr<Foo> makeFoo(int x) {
std::unique_ptr<Foo> result = nullptr; 
if (x > 10) { 
result = std::make_unique<Foo>(true); 
}
else {
result = std::make_unique<Foo>(false); 
}
return result;
}

要使上述内容正常工作,您还需要包含标头。 有关 C++ 中unique_ptr和内存管理的更多信息,请参阅此处

另外,请注意,我不建议在代码中使用这种级别的冗长,我写出来是为了演示目的。以下版本提供的代码行更少,更优雅。

std::unique_ptr<Foo> makeFoo(int x) {
if (x > 10) { 
return std::make_unique<Foo>(true); 
}
return std::make_unique<Foo>(false); 
}