10

我通常几乎不加思索地使用前向声明,这样我就不必包含标题。这个例子中的一些东西:

//-----------------------
// 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;
};

所以,如果有人知道可以在这里使用的替代语言结构,我会很高兴,这样我就可以声明“foo_object”,如示例中所示,但不包括其标题。

问候

/罗伯特

4

10 回答 10

12

你不能。编译器在声明类时需要知道对象的大小。

引用是一种替代方法,尽管它们必须在构造时实例化,所以它并不总是可行的。

另一种选择是智能指针,但我想这在技术上仍然是一个指针。

很高兴知道为什么您不想使用指针来建议其他一些构造...

于 2008-11-20T16:17:30.860 回答
8

只需使用智能指针 - 在这种情况下您甚至可以使用 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;
}
于 2008-11-20T16:38:46.153 回答
7

你想要的东西不能在 C++ 中完成。为了为一个对象生成代码,你的编译器需要知道它的类需要多少存储空间。为了知道这一点,它必须知道类的每个成员需要多少存储空间。

如果你想用 foo 类型的成员创建一个 bar 类型的类,编译器必须知道 foo 有多大。它知道的唯一方法是它是否具有可用的 foo 定义(通过#include)。否则,您唯一的选择是使用 foo 的前向声明和指针或引用,而不是实际的 foo 对象。

于 2008-11-20T16:21:04.910 回答
2

正如其他人所说,由于他们所说的原因,您不能这样做:) 然后您说您不想关心包含它们的类中的成员构造/破坏。您可以为此使用模板。

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;
};

我认为代码是不言自明的。如果出现任何问题,请打扰我。

于 2008-11-20T16:53:29.773 回答
1

没有办法解决这个问题。

最好的办法是限制包含的数量,但您必须在类声明中包含文件。您可以将类声明拆分为一个单独的标头,希望它不包含其他任何内容。那么是的,你必须有一个#include,但你仍然保持你的包含层次结构有点浅。毕竟,包括一个文件很便宜,只有当层次结构扩展到数百或数千个文件时,它才会开始受到伤害......;)

于 2008-11-20T16:34:14.790 回答
1

几乎你唯一能做的就是通过使用 pImpl 习惯用法来最小化影响,这样当你包含 foo.h 时,你只包含 foo 的接口。

你无法避免包含 foo.h,但你可以让它尽可能便宜。您养成的使用 foward 声明而不是#inlcudes 的习惯使您在这条道路上做得很好。

于 2008-11-20T16:36:32.010 回答
0

如果您能够使用引用,则可以保留相同的使用语法。但是,您的引用必须立即在构造函数中初始化,因此您的 ctor 绝对必须离线定义。(您还需要在析构函数中释放对象。)

// bar.h
class foo;

class bar {
    foo& foo_;

public:
    bar();
    ~bar();
};

// bar.cc
bar::bar() : foo_(*new foo)
{
    // ...
}

bar::~bar()
{
    // ...
    delete &foo_;
}

你的旅费可能会改变。:-)

于 2008-11-20T16:16:47.243 回答
0

您可以使用自动创建和销毁实例的自定义“智能指针”类。这将实现您所追求的自动构建和破坏。

为了避免需要另一个#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;
}
于 2008-11-20T16:49:59.907 回答
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 习语,它对编译时间有很大影响。可惜该语言无法处理真正私有的实现,但是您已经拥有了。

于 2008-11-20T18:13:36.180 回答
0

实际上只有三种选择可以关联两个对象。您已经发现了两个:将 Foo 嵌入 Bar 中,或者将 Foo 放在堆上并在 Bar 中放入 Foo*。第一个需要在定义类 Bar 之前定义类 Foo;第二个只需要你转发声明类Foo。

确实存在第三个选项,我只提到它是因为您在问题中明确排除了先前的两个选项。您可以(在您的 .cpp 中)创建静态 std::map。在每个 Bar 构造函数中,您将 Foo 添加到此地图,键入this. this然后,每个酒吧成员都可以通过在地图中查找来找到相关的 Foo 。Bar::~Bar 将调用erase(this)销毁 Foo。

虽然这使 sizeof(Bar) 保持不变,但实际内存使用量高于在 Bar 中包含 Foo*。但是,如果二进制兼容性是一个紧迫的问题,您仍然可以这样做。

于 2008-11-21T10:33:44.893 回答