Igo*_*Oks 588 c++ c++-faq forward-declaration
我正在寻找允许在另一个类的头文件中进行类的前向声明的定义:
我是否允许为基类,作为成员持有的类,通过引用传递给成员函数的类等执行此操作?
Luc*_*lle 937
让自己处于编译器的位置:当你转发声明一个类型时,所有编译器都知道这个类型存在; 它对它的大小,成员或方法一无所知.这就是为什么它被称为不完整类型.因此,您不能使用该类型来声明成员或基类,因为编译器需要知道该类型的布局.
假设以下前瞻性声明.
class X;
Run Code Online (Sandbox Code Playgroud)
这是你能做什么,不能做的.
你可以用不完整的类型做什么:
将成员声明为指针或对不完整类型的引用:
class Foo {
X *p;
X &r;
};
Run Code Online (Sandbox Code Playgroud)声明接受/返回不完整类型的函数或方法:
void f1(X);
X f2();
Run Code Online (Sandbox Code Playgroud)定义接受/返回指向不完整类型的指针/引用的函数或方法(但不使用其成员):
void f3(X*, X&) {}
X& f4() {}
X* f5() {}
Run Code Online (Sandbox Code Playgroud)你不能用不完整的类型做什么:
将它用作基类
class Foo : X {} // compiler error!
Run Code Online (Sandbox Code Playgroud)用它来声明一个成员:
class Foo {
X m; // compiler error!
};
Run Code Online (Sandbox Code Playgroud)使用此类型定义函数或方法
void f1(X x) {} // compiler error!
X f2() {} // compiler error!
Run Code Online (Sandbox Code Playgroud)使用其方法或字段,实际上尝试取消引用具有不完整类型的变量
class Foo {
X *m;
void method()
{
m->someMethod(); // compiler error!
int i = m->someField; // compiler error!
}
};
Run Code Online (Sandbox Code Playgroud)对于模板,没有绝对规则:是否可以使用不完整类型作为模板参数取决于模板中使用类型的方式.
例如,std::vector<T>要求其参数为完整类型,而boost::container::vector<T>不是.有时,只有在使用某些成员函数时才需要完整类型; 例如,情况就是如此std::unique_ptr<T>.
记录良好的模板应在其文档中指出其参数的所有要求,包括它们是否需要完整类型.
Tim*_*sch 43
主要规则是,您只能转发声明其内存布局(以及成员函数和数据成员)不需要在您转发声明的文件中知道的类.
这将排除基类和除引用和指针使用的类之外的任何东西.
j_r*_*ker 28
除了指向不完整类型的指针和引用之外,您还可以声明指定参数和/或返回不完整类型值的函数原型.但是,除非是指针或引用,否则无法定义具有不完整的参数或返回类型的函数.
例子:
struct X; // Forward declaration of X
void f1(X* px) {} // Legal: can always use a pointer
void f2(X& x) {} // Legal: can always use a reference
X f3(int); // Legal: return value in function prototype
void f4(X); // Legal: parameter in function prototype
void f5(X) {} // ILLEGAL: *definitions* require complete types
Run Code Online (Sandbox Code Playgroud)
R S*_*ahu 18
到目前为止,没有一个答案描述何时可以使用类模板的前向声明.所以,在这里.
可以将类模板转发声明为:
template <typename> struct X;
Run Code Online (Sandbox Code Playgroud)
遵循接受的答案的结构,
这是你能做什么,不能做的.
你可以用不完整的类型做什么:
将成员声明为指针或对另一个类模板中不完整类型的引用:
template <typename T>
class Foo {
X<T>* ptr;
X<T>& ref;
};
Run Code Online (Sandbox Code Playgroud)声明一个成员是一个指针或对其不完整实例之一的引用:
class Foo {
X<int>* ptr;
X<int>& ref;
};
Run Code Online (Sandbox Code Playgroud)声明接受/返回不完整类型的函数模板或成员函数模板:
template <typename T>
void f1(X<T>);
template <typename T>
X<T> f2();
Run Code Online (Sandbox Code Playgroud)声明接受/返回其不完整实例化的函数或成员函数:
void f1(X<int>);
X<int> f2();
Run Code Online (Sandbox Code Playgroud)定义接受/返回指向不完整类型的指针/引用的函数模板或成员函数模板(但不使用其成员):
template <typename T>
void f3(X<T>*, X<T>&) {}
template <typename T>
X<T>& f4(X<T>& in) { return in; }
template <typename T>
X<T>* f5(X<T>* in) { return in; }
Run Code Online (Sandbox Code Playgroud)定义接受/返回其不完整实例之一(但不使用其成员)的指针/引用的函数或方法:
void f3(X<int>*, X<int>&) {}
X<int>& f4(X<int>& in) { return in; }
X<int>* f5(X<int>* in) { return in; }
Run Code Online (Sandbox Code Playgroud)将它用作另一个模板类的基类
template <typename T>
class Foo : X<T> {} // OK as long as X is defined before
// Foo is instantiated.
Foo<int> a1; // Compiler error.
template <typename T> struct X {};
Foo<int> a2; // OK since X is now defined.
Run Code Online (Sandbox Code Playgroud)用它来声明另一个类模板的成员:
template <typename T>
class Foo {
X<T> m; // OK as long as X is defined before
// Foo is instantiated.
};
Foo<int> a1; // Compiler error.
template <typename T> struct X {};
Foo<int> a2; // OK since X is now defined.
Run Code Online (Sandbox Code Playgroud)使用此类型定义函数模板或方法
template <typename T>
void f1(X<T> x) {} // OK if X is defined before calling f1
template <typename T>
X<T> f2(){return X<T>(); } // OK if X is defined before calling f2
void test1()
{
f1(X<int>()); // Compiler error
f2<int>(); // Compiler error
}
template <typename T> struct X {};
void test2()
{
f1(X<int>()); // OK since X is defined now
f2<int>(); // OK since X is defined now
}
Run Code Online (Sandbox Code Playgroud)你不能用不完整的类型做什么:
使用其中一个实例作为基类
class Foo : X<int> {} // compiler error!
Run Code Online (Sandbox Code Playgroud)使用其中一个实例来声明成员:
class Foo {
X<int> m; // compiler error!
};
Run Code Online (Sandbox Code Playgroud)使用其中一个实例来定义函数或方法
void f1(X<int> x) {} // compiler error!
X<int> f2() {return X<int>(); } // compiler error!
Run Code Online (Sandbox Code Playgroud)使用其中一个实例化的方法或字段,实际上是尝试取消引用具有不完整类型的变量
class Foo {
X<int>* m;
void method()
{
m->someMethod(); // compiler error!
int i = m->someField; // compiler error!
}
};
Run Code Online (Sandbox Code Playgroud)创建类模板的显式实例化
template struct X<int>;
Run Code Online (Sandbox Code Playgroud)在只使用指针或引用类的文件中.如果指针/引用,则不应调用任何成员/成员函数.
与class Foo;//前向声明
我们可以声明Foo*或Foo&类型的数据成员.
我们可以使用Foo类型的参数和/或返回值声明(但不定义)函数.
我们可以声明Foo类型的静态数据成员.这是因为静态数据成员是在类定义之外定义的.
我将其写为一个单独的答案,而不仅仅是一条评论,因为我不同意 Luc Touraille 的答案,不是因为合法性,而是因为强大的软件和误解的危险。
具体来说,我对您期望界面用户必须了解的隐含合同有疑问。
如果您返回或接受引用类型,那么您只是说它们可以传递指针或引用,而它们可能只能通过前向声明才能知道。
当您返回不完整的类型X f2();时,您是在说您的调用者必须具有 X 的完整类型规范。他们需要它才能在调用站点创建 LHS 或临时对象。
同样,如果您接受不完整的类型,则调用者必须构造作为参数的对象。即使该对象作为函数中的另一个不完整类型返回,调用站点也需要完整的声明。IE:
class X; // forward for two legal declarations
X returnsX();
void XAcceptor(X);
XAcepptor( returnsX() ); // X declaration needs to be known here
Run Code Online (Sandbox Code Playgroud)
我认为有一个重要的原则,即标头应该提供足够的信息来使用它,而无需依赖其他标头。这意味着标头应该能够包含在编译单元中,而在使用它声明的任何函数时不会导致编译器错误。
除了
如果这种外部依赖是期望的行为。您可以不使用条件编译,而需要有详细记录的要求,要求它们提供自己的标头声明 X。这是使用 #ifdefs 的替代方法,并且可以是引入模拟或其他变体的有用方法。
重要的区别是一些模板技术,您明确不希望实例化它们,提及只是为了避免有人对我尖酸刻薄。