C++ 允许仅通过函数声明来实例化对象

yap*_*m01 0 c++ c++11 c++20

下面简单的代码。

class Base{
public:
    int fcn();
};

int main() {
   Base b; // clause 1
}
Run Code Online (Sandbox Code Playgroud)

为什么会这样编译?第 1 条创建了一个实例,但从未定义Base该函数。fcn()

for*_*818 5

原因Base b;不是编译器错误,而是因为编译器一般无法知道是否缺少定义。

您发布的代码可能是完整的翻译单元,而定义位于不同的翻译单元中。只有当需要定义(例如调用函数)但找不到定义时,链接器才会发出错误。


实际上,在很多情况下,人们希望声明某些内容但不定义(或仅有条件地定义)。下面是两个例子。


假设您有一个带有参数的方法double,并且您希望阻止用户使用int. 隐式转换可能很烦人,基本类型的隐式转换更令人烦恼。人们可以做这样的事情:

struct foo {
    void do_something(double) {}
};
struct bar {
    void do_something(double) {}
    void do_something(int);      // no definition !!
};
    
int main()
{
    foo{}.do_something(1);
    bar{}.do_something(1);
}
Run Code Online (Sandbox Code Playgroud)

foo::do_something(double)可以用int. 另一方面,bar::do_something(double)必须bar::do_something(int)在重载解析中竞争并bar{}.do_something(1);导致链接器错误。

请注意,这里有更好的方法来获取更好的编译器错误消息(= delete自 C++11 以来)。然而,重点是:只要你只bar::do_somethingdoubleall 跟注就可以了。没有错误。并且不会出现任何错误。它可以工作并且是完全有效的 C++。


另一个例子是用于区分模板的不同实例的标签类型:

struct tag1;   // no definition !!
struct tag2;   // no defniition !!

template <typename T> struct foo;
template <> struct foo<tag1> { /* something */ };
template <> struct foo<tag2> { /* something else */ };

int main() {
    foo<tag1> a;
    foo<tag2> b;
}
Run Code Online (Sandbox Code Playgroud)

这完全没问题,因为模板不会执行任何要求其参数为完整类型的操作。仅使用类型来标记模板的实例或选择重载是一种常见技术,有时标记所需的只是一个声明。

补充一下,这与您的示例不同,因为它缺少整个类定义,并且没有创建类的实例。尽管我会把它放在同一个包中:对于没有定义的声明很有用。