当你不想#include时替代前向声明
-
08-07-2019 - |
题
我通常几乎不再思考,使用前向声明,这样我就不必包含标题。这个例子的一些东西:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo; // forward declaration
class bar
{
bar();
~bar();
foo* foo_pointer;
};
一些开发人员喜欢使用此方法来避免包含圈子出现问题。我宁愿用它来减少广泛的包含层次结构的开销,这是物理设计的一个重要部分(特别是对于大型项目)。
但是,在某些情况下,我真的想将成员声明为普通对象而不是指针,以便从自动构建/销毁机制中受益。这导致了前向声明不能再使用的问题,因为在这种情况下编译器需要类定义,例如:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo; // Not enough given the way we declare "foo_object"..
#include "foo.h" // ..instead this is required
class bar
{
bar();
~bar();
foo foo_object;
};
所以,如果有人知道可以在这里使用的替代语言构造,我会很高兴我可以声明<!> quot; foo_object <!> quot;如示例所示,但不包括其标题。
此致
/罗伯特
解决方案
只需使用智能指针 - 在这种情况下甚至可以使用auto_ptr。
//-----------------------
// bar.h
//-----------------------
#include <memory>
class foo; // Not enough given the way we declare "foo_object"..
class bar
{
public:
bar();
~bar();
foo &foo_object() { return *foo_ptr; }
const foo &foo_object() const { return *foo_ptr; }
private:
auto_ptr<foo> foo_ptr;
};
您可以获得自动内存管理的所有好处,而无需在bar.h中了解有关foo的任何信息。有关Herb Sutter的建议,请参见包装指针数据成员。
如果你真的希望自动发生默认构造,试试这个:
#include <iostream>
using namespace std;
class Foo;
template <typename T>
class DefaultConstuctorPtr
{
T *ptr;
void operator =(const DefaultConstuctorPtr &);
DefaultConstuctorPtr(const DefaultConstuctorPtr &);
public:
DefaultConstuctorPtr() : ptr(new T()) {}
~DefaultConstuctorPtr() { delete ptr; }
T *operator *() { return ptr; }
const T *operator *() const { return ptr; }
};
class Bar
{
DefaultConstuctorPtr<Foo> foo_ptr;
public:
Bar() {} // The compiler should really need Foo() to be defined here?
};
class Foo
{
public:
Foo () { cout << "Constructing foo"; }
};
int main()
{
Bar bar;
}
其他提示
你做不到。在声明类时,编译器需要知道对象的大小。
参考是一种替代方案,虽然它们必须在施工时进行实例化,因此并不总是可行。
另一种选择是智能指针,但我认为技术上仍然是指针。
很高兴知道为什么你不想使用指针建议其他一些构造虽然......
你想要的东西不能用C ++完成。为了生成对象的代码,您的编译器需要知道它的类需要多少存储空间。为了解这一点,它必须知道每个成员需要多少存储空间。
如果要创建类型为bar的类型为bar的类型,编译器必须知道foo的大小。它知道的唯一方法是它是否有可用的foo定义(通过#include)。否则,您唯一的选择是使用foo的前向声明和指针或引用而不是实际的foo对象。
正如其他人所说,你不能因为他们所说的原因而这样做:)然后你说你不想关心包含它们的类中的成员构造/破坏。您可以使用模板。
template<typename Type>
struct member {
boost::shared_ptr<Type> ptr;
member(): ptr(new Type) { }
};
struct foo;
struct bar {
bar();
~bar();
// automatic management for m
member<foo> m;
};
我认为代码是自我解释的。如果出现任何问题,请告诉我。
没有办法解决这个问题。
您最好的选择是限制包含的内容,但您必须将该文件包含在类声明中。您可以将类声明拆分为一个单独的标题,希望其中不包含任何其他标题。然后是的,你必须有一个#include,但你仍然保持你的包含层次有点浅。毕竟,包括一个文件是便宜的,只有当层次结构延伸到数百或数千个文件时才开始受到伤害......;)
你唯一可以做的就是通过使用pImpl习语,这样当你包含foo.h时,你只包括foo的界面。
你不能避免包括foo.h,但你可以尽可能便宜。你使用foward声明而不是#inlcudes开发的习惯让你在这条道路上走得很好。
如果您能够使用引用,则可以保留相同的使用语法。但是,您的引用必须在构造函数中立即初始化,因此您的ctor必须绝对定义为out-of-line。 (您还需要在析构函数中释放该对象。)
// bar.h
class foo;
class bar {
foo& foo_;
public:
bar();
~bar();
};
// bar.cc
bar::bar() : foo_(*new foo)
{
// ...
}
bar::~bar()
{
// ...
delete &foo_;
}
您的里程可能会有所不同。 : - )
您可以使用自定义<!>“智能指针<!>”;自动创建和销毁实例的类。这将实现您所追求的自动构造和破坏。
为了防止需要另一个#include,您可以在项目的前缀标题中包含此myAuto
类,或者您可以将其复制并粘贴到每个标题中(不是一个好主意,但它会起作用)。
template<class T>
class myAuto
{
private:
T * obj;
public:
myAuto() : obj(new T) { }
~myAuto() { delete obj; }
T& object() { return *obj; }
T* operator ->() { return obj; }
};
以下是您将如何使用它:
// foo.h:
class foo
{
public:
foo();
~foo();
void some_foo_func();
};
//bar.h:
class foo;
class bar
{
public:
bar();
~bar();
myAuto<foo> foo_object;
};
//main.cc:
#include "foo.h"
#include "bar.h"
int main()
{
bar a_bar;
a_bar.foo_object->some_foo_func();
return 0;
}
你也可以使用pImpl习语,例如:
//-----------------------
// foo.h
//-----------------------
class foo
{
foo();
~foo();
};
//-----------------------
// bar.h
//-----------------------
class foo;
class bar
{
private:
struct impl;
boost::shared_ptr<impl> impl_;
public:
bar();
const foo& get_foo() const;
};
//-----------------------
// bar.cpp
//-----------------------
#include "bar.h"
#include "foo.h"
struct bar::impl
{
foo foo_object;
...
}
bar::bar() :
impl_(new impl)
{
}
const foo& bar::get_foo() const
{
return impl_->foo_object;
}
您仍然可以获得前向声明的好处,另外还可以隐藏您的私有实现。对bar实现的更改不一定需要编译#include bar.h的所有源文件。实现结构本身在.cpp文件中是自包含的,在这里你可以向你的内容声明对象。
由于pImpl本身的影响很小,但是根据应用程序的不同,这可能不是什么大问题。
我已经将pImpl习惯用于大型项目,这对编译时间有很大影响。可惜语言无法处理真正的私有实现,但是你有它。
实际上只有三种方法可以关联两个对象。 你已经发现了两个:在Bar中嵌入Foo,或者把Foo放在堆上,并在一个Foo *中添加一个Foo *。第一个要求在定义类Bar之前定义类Foo;第二个只需要你转发声明类Foo。
确实存在第三种选择,我只提及,因为您明确排除了问题中的前两个选项。您可以(在您的.cpp中)创建一个静态std :: map。在每个Bar构造函数中,您都会向此地图添加一个Foo,键入this
。然后,每个酒吧成员都可以通过在地图中查找erase(this)
来查找关联的Foo。 Bar :: ~Bar会调用<=>来销毁Foo。
虽然这保持sizeof(Bar)不变,但实际内存使用率高于在Bar中包含Foo *。如果二进制兼容性是一个紧迫的问题,你仍然可以这样做。