当您不想 #include 时转发声明的替代方案

发布于 2024-07-09 02:02:51 字数 972 浏览 5 评论 0原文

我通常几乎不假思索地使用前向声明,这样我就不必包含标头。 此示例中的一些内容:

//-----------------------
// 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”,但不包含其标头。

问候

/罗伯特

I usually, almost without thinking anymore, use forward declarations so that I won't have to include headers. Something along this example:

//-----------------------
// foo.h
//-----------------------
class foo
{
   foo();
   ~foo();
};


//-----------------------
// bar.h
//-----------------------

class foo; // forward declaration

class bar
{
   bar();
   ~bar();

   foo* foo_pointer;
};

Some developers like to use this method to avoid problems with inclusion circles. I rather use it to minimize the overhead in extensive inclusion hierarchies, an important part of physical design (for larger projects in particular).

However, in some cases I really like to declare members as normal objects instead of pointers to benefit from the automatic construction/destruction mechanism. This leads to the problem that forward declarations can't be used anymore, since the compiler needs the class definition in such case, eg:

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

So, I would be happy if anyone knows an alternative language construct which can be used here so that I can declare "foo_object" as shown in the example, but without including its header.

Regards

/Robert

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

风追烟花雨 2024-07-16 02:02:51

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

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

另一种选择是智能指针,但我认为从技术上讲这仍然是一个指针。

不过,最好知道为什么您不想使用指针来建议其他构造......

You can't. The compiler needs to know the size of the object when declaring the class.

References are an alternative, although they have to be instantiated at construction time, so it's not always feasible.

Another alternative are smart pointers, but I suppose that's technically still a pointer.

It would be good to know why you don't want to use a pointer to suggest some other construct though...

云巢 2024-07-16 02:02:51

只需使用智能指针 - 在这种情况下您甚至可以使用 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;
}

Just use a smart pointer - you can even use auto_ptr in this case.

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

You get all the benefits of automatic memory management, without having to know anything about foo in bar.h. See Wrapping Pointer Data Members for Herb Sutter's recommendation.

If you really want default construction to happen automatically, try this:

#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++中是做不到的。 为了生成对象的代码,编译器需要知道其类需要多少存储空间。 为了知道这一点,它必须知道类的每个成员需要多少存储空间。

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

What you want cannot be done in C++. In order to generate code for an object, your compiler needs to know how much storage its class requires. In order to know that, it must know how much storage is required for each member of the class.

If you want to create a class of type bar with a member of type foo, the compiler has to know how big a foo is. The only way it knows that is if it has the definition of foo available (via #include). Otherwise, your only option is to use a forward-declaration of foo and a pointer or reference instead of an actual foo object.

你げ笑在眉眼 2024-07-16 02:02:51

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

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

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

As others stated you cannot do it for reasons they stated too :) You then said you don't want to care about the member construction / destruction in the class containing them. You can use templates for this.

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

I think the code is self explanatory. If any questions arise, bug me please.

梦纸 2024-07-16 02:02:51

没有办法解决这个问题。

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

There's no way around that.

Your best bet is to limit how much is included, but you have to include the file with the class declaration. You could could split the class declarationout into a separate header which, hopefully, includes nothing else. Then yes, you have to have an #include, but you're still keeping your include hierarchy somewhat shallow. After all, including one file is cheap, it's only when the hierarchy stretches out to hundreds or thousands of files that it starts hurting... ;)

蓝咒 2024-07-16 02:02:51

几乎您唯一能做的就是通过 使用 pImpl 习惯用法,这样当您包含 foo.h 时,您只包含 foo 的接口。

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

Pretty much the only thing you can do is minimize the impact by using the pImpl idiom so that when you include foo.h, you're only including foo's interface.

You can't avoid including foo.h, but you can make it as cheap as possible. The habit you've developed of using foward declarations rather than #inlcudes has you well on this path.

看透却不说透 2024-07-16 02:02:51

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

// bar.h
class foo;

class bar {
    foo& foo_;

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

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

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

您的情况可能会有所不同。 :-)

If you are able to use a reference, you can retain the same use syntax. However, your reference has to be initialised straight away in the constructor, so your ctor absolutely must be defined out-of-line. (You will also need to free the object in the destructor too.)

// bar.h
class foo;

class bar {
    foo& foo_;

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

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

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

Your mileage may vary. :-)

失退 2024-07-16 02:02:51

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

为了防止需要另一个 #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;
}

You could use a custom "smart pointer" class which automatically creates and destroys an instance. This would acheive the automatic construction and destruction you are after.

To prevent the need for another #include, you can include this myAuto class in the prefix header for your project, or you can copy and paste it into every header (not a good idea, but it would work).

template<class T>
class myAuto
{
    private:
        T * obj;

    public:
        myAuto() : obj(new T) {  }
        ~myAuto() { delete obj; }
        T& object() { return *obj; }
        T* operator ->() { return obj; }
};

Here is how you would use it:

// 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;
}
2024-07-16 02:02:51

您还可以使用 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 习惯用法,它对编译时间产生了很大的影响。 遗憾的是,该语言无法处理真正的私有实现,但你已经拥有了。

You could also use the pImpl idiom, e.g.:

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

You still get the benefits of forward declarations, plus you hide your private implementation. Changes to the implementation of bar won't necessarily require compiling all the source files that #include bar.h. The implementation struct itself is self-contained in the .cpp file and here you can declare objects to your hearts content.

You have a small performance hit because of pImpl itself, but depending on the application this might not be a big deal.

I have used the pImpl idiom for large projects and it makes a big difference to compile times. Pity the language cannot handle a truly private implementation, but there you have it.

っ左 2024-07-16 02:02:51

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

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

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

There are really only three alternatives to associate two objects.
You already discovered two: embed Foo in Bar, or put Foo on the heap and put a Foo* in Bar. The first requires defining class Foo before defining class Bar; the second merely requires you to forward declare class Foo.

A third option does exist, which I only mention because you specifically exclude both previous options in your question. You can (in your .cpp) create a static std::map. In every Bar constructor you add a Foo to this map, keyed on this. Every bar member can then find the associated Foo by looking up this in the map. Bar::~Bar will call erase(this) to destroy the Foo.

While this keeps sizeof(Bar) unchanged, the actual memory use is higher than including a Foo* in Bar. You still might do this if binary compatibility is a pressing concern, though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文