下面是简单的代码
class Base{
public:
int fcn();
};
int main() {
Base b; // clause 1
}
为什么可以编译?子句1创建了一个Base
实例,但是fcn()
函数没有定义。
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);
}
foo::do_something(double)
可以和int
一起调用。另一方面,bar::do_something(double)
必须在重载解析上与bar::do_something(int)
竞争,而bar{}.do_something(1);
会导致链接器错误。
请注意,这里有更好的方法来获得更好的编译器错误消息(自c++ 11以来的= delete
)。然而,关键是:只要您只使用double
调用bar::do_something
,一切都没问题。没有错误。而且不会出现错误。
另一个例子是用于区分模板的不同实例化的标签类型:
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;
}
这完全没问题,因为模板不做任何需要其实参为完整类型的操作。使用类型仅仅标记模板的实例化或选择重载是一种常见的技术,有时你所需要的标记只是一个声明。
诚然,这与您的示例不同,因为它缺少整个类定义,并且没有创建类的实例。虽然我会把它放在同一个袋子里:有一个没有定义的声明很有用。