在带有模板构造函数的类中使用智能指针的 Pimpl:奇怪的不完整类型问题

发布于 2024-10-16 05:46:25 字数 3559 浏览 7 评论 0原文

当将智能指针与 pImpl 习惯用法一起使用时,

struct Foo
{
private:
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl;
};

明显的问题是 Foo::Impl 在生成 Foo 的析构函数时不完整。

编译器通常会在那里发出警告,而 Boost 智能指针在内部使用的 boost::checked_delete 静态断言类 Foo::Impl 已完成并触发如果不是这种情况,则会出错。

因此,为了编译上面的示例,必须

struct Foo
{
    ~Foo();

private:
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl;
};

在实现文件中编写并实现一个空的 Foo::~Foo,其中 Foo::Impl 已完成。这是智能指针相对于裸指针的优势,因为我们不能不实现析构函数。

到目前为止,一切都很好。但当我尝试在类似的 Bar 类中引入模板构造函数时,我遇到了一个奇怪的行为(完整代码,请自行尝试):

// File Bar.h
#ifndef BAR_H
#define BAR_H 1

#include <vector>
#include <boost/scoped_ptr.hpp>

struct Bar
{
    template <typename I>
    Bar(I begin, I end);

    ~Bar();

private:
    struct Impl;
    boost::scoped_ptr<Impl> pImpl;

    void buildImpl(std::vector<double>&);
};


template <typename I>
Bar::Bar(I begin, I end)
{
    std::vector<double> tmp(begin, end);
    this->buildImpl(tmp);
}

#endif // BAR_H

// File Bar.cpp
#include "Bar.h"

struct Bar::Impl
{
    std::vector<double> v;
};

void Bar::buildImpl(std::vector<double>& v)
{
    pImpl.reset(new Impl);
    pImpl->v.swap(v);
}

Bar::~Bar() {}

// File Foo.h
#ifndef FOO_H
#define FOO_H 1

#include <boost/scoped_ptr.hpp>


struct Foo
{
    Foo();
    ~Foo();

private:
    struct Impl;
    boost::scoped_ptr<Impl> pImpl;
};

#endif // FOO_H

// File Foo.cpp
#include "Foo.h"

struct Foo::Impl
{};


Foo::Foo() : pImpl(new Impl)
{}


Foo::~Foo() {}


// File Main.cpp
#include "Foo.h"
#include "Bar.h"

int main()
{
    std::vector<double> v(42);
    Foo f;
    Bar b(v.begin(), v.end());
}

当使用 Visual Studio 2005 SP1 编译此示例时,我得到一个Bar 出错,但 Foo 没有:

1>Compiling...
1>main.cpp
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2027: use of undefined type 'Bar::Impl'
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl'
1>        c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(80) : see reference to function template instantiation 'void boost::checked_delete<T>(T *)' being compiled
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>        c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(76) : while compiling class template member function 'boost::scoped_ptr<T>::~scoped_ptr(void)'
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(16) : see reference to class template instantiation 'boost::scoped_ptr<T>' being compiled
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2118: negative subscript
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(34) : warning C4150: deletion of pointer to incomplete type 'Bar::Impl'; no destructor called
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl'

我一回家就会用最近的 gcc 尝试这个。

我不明白发生了什么:在定义析构函数的地方(即在 Bar.cpp 中),可以使用 Bar::Impl 的定义,所以应该不会有任何问题。为什么这适用于 Foo 而不适用于 Bar

我在这里缺少什么?

When using smart pointers with the pImpl idiom, as in

struct Foo
{
private:
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl;
};

the obvious problem is that Foo::Impl is incomplete at the point where the destructor of Foo is generated.

Compilers usually emit a warning there, and boost::checked_delete, which is used internally by Boost smart pointers, statically asserts that the class Foo::Impl is complete and triggers an error if it is not the case.

For the above example to compile, one therefore must write

struct Foo
{
    ~Foo();

private:
    struct Impl; 
    boost::scoped_ptr<Impl> pImpl;
};

and implement an empty Foo::~Foo in the implementation file, where Foo::Impl is complete. This is an advantage of smart pointers over bare pointers, because we can't fail to implement the destructor.

So far, so good. But I came across a weird behaviour when I try to introduce a template constructor in a similar Bar class (full code, please try it yourself):

// File Bar.h
#ifndef BAR_H
#define BAR_H 1

#include <vector>
#include <boost/scoped_ptr.hpp>

struct Bar
{
    template <typename I>
    Bar(I begin, I end);

    ~Bar();

private:
    struct Impl;
    boost::scoped_ptr<Impl> pImpl;

    void buildImpl(std::vector<double>&);
};


template <typename I>
Bar::Bar(I begin, I end)
{
    std::vector<double> tmp(begin, end);
    this->buildImpl(tmp);
}

#endif // BAR_H

// File Bar.cpp
#include "Bar.h"

struct Bar::Impl
{
    std::vector<double> v;
};

void Bar::buildImpl(std::vector<double>& v)
{
    pImpl.reset(new Impl);
    pImpl->v.swap(v);
}

Bar::~Bar() {}

// File Foo.h
#ifndef FOO_H
#define FOO_H 1

#include <boost/scoped_ptr.hpp>


struct Foo
{
    Foo();
    ~Foo();

private:
    struct Impl;
    boost::scoped_ptr<Impl> pImpl;
};

#endif // FOO_H

// File Foo.cpp
#include "Foo.h"

struct Foo::Impl
{};


Foo::Foo() : pImpl(new Impl)
{}


Foo::~Foo() {}


// File Main.cpp
#include "Foo.h"
#include "Bar.h"

int main()
{
    std::vector<double> v(42);
    Foo f;
    Bar b(v.begin(), v.end());
}

When compiling this example with Visual Studio 2005 SP1, I get an error with Bar but not with Foo:

1>Compiling...
1>main.cpp
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2027: use of undefined type 'Bar::Impl'
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl'
1>        c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(80) : see reference to function template instantiation 'void boost::checked_delete<T>(T *)' being compiled
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>        c:\users\boost_1_45_0\boost\smart_ptr\scoped_ptr.hpp(76) : while compiling class template member function 'boost::scoped_ptr<T>::~scoped_ptr(void)'
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(16) : see reference to class template instantiation 'boost::scoped_ptr<T>' being compiled
1>        with
1>        [
1>            T=Bar::Impl
1>        ]
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(32) : error C2118: negative subscript
1>c:\users\boost_1_45_0\boost\checked_delete.hpp(34) : warning C4150: deletion of pointer to incomplete type 'Bar::Impl'; no destructor called
1>        c:\users\visual studio 2005\projects\checkeddeletetest\checkeddeletetest\bar.h(15) : see declaration of 'Bar::Impl'

I will try this with a recent gcc as soon as I get home.

I don't understand what is going on: at the point where the destructor is defined (ie. in Bar.cpp), a definition of Bar::Impl is available, so there should not be any problem. Why does this work with Foo and not with Bar?

What am I missing here ?

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

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

发布评论

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

评论(2

若能看破又如何 2024-10-23 05:46:25

这是 boost::shared_ptr<> 的析构函数,要求在使用 boost::checked_deleter<> 删除器时对象是完整的。因为您将范围构造函数 Bar::Bar(I begin, I end) 放在头文件中,所以如果构造函数抛出异常,编译器必须生成销毁已构造成员的代码,因此它会尝试实例化 < code>boost::scoped_ptr::~scoped_ptr(void) 实例化此模板构造函数时。

将智能指针与 pimpl 一起使用不太有用。由于您通常需要提供一个析构函数,因此您也可以将 delete pimpl 放入该析构函数中并完成此操作。

This is the destructor of boost::shared_ptr<> that requires the object to be complete when using boost::checked_deleter<> deleter. Because you put the range constructor Bar::Bar(I begin, I end) in the header file the compiler must generate code that destroys already constructed members if your constructor throws, hence it is trying to instantiate boost::scoped_ptr<T>::~scoped_ptr(void) when instantiating this template constructor.

It is less than useful to use smart pointers with pimpl. Since you normally need to provide a destructor anyway, you could as well put delete pimpl in that destructor and be done with that.

琴流音 2024-10-23 05:46:25

来自 Boost Boost 文档

请注意,scoped_ptr 要求 T 在销毁时是完整类型,但 shared_ptr 则不需要。

切换到shared_ptr,一切都应该很好——也不需要析构函数(空或其他)。如果您想让类成为不可复制的,以便它具有从scoped_ptr 获得的语义,请从boost::noncopyable 继承(私有)。

From the Boost Boost documentation:

Note that scoped_ptr requires that T be a complete type at destruction time, but shared_ptr does not.

Switch to shared_ptr and all should be well - no need to have a destructor either (empty or otherwise). If you want to make the class noncopyable so it has the semantics you'd have got from scoped_ptr, inherit (privately) from boost::noncopyable.

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