Khi sử dụng con trỏ thông minh với các thành ngữ pImpl, như trongPimpl với con trỏ thông minh trong một lớp học với một mẫu constructor: lạ không đầy đủ loại vấn đề
struct Foo
{
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
vấn đề rõ ràng là Foo::Impl
là chưa đầy đủ tại điểm nơi destructor của Foo
được tạo.
Trình biên dịch thường phát ra cảnh báo ở đó và boost::checked_delete
, được sử dụng nội bộ bởi con trỏ thông minh Boost, xác nhận tĩnh rằng lớp Foo::Impl
hoàn tất và kích hoạt lỗi nếu không phải như vậy.
Đối với ví dụ trên để biên dịch, một do đó phải viết
struct Foo
{
~Foo();
private:
struct Impl;
boost::scoped_ptr<Impl> pImpl;
};
và thực hiện một trống Foo::~Foo
trong file thực hiện, nơi Foo::Impl
hoàn tất. Đây là một lợi thế của con trỏ thông minh trên con trỏ trần, bởi vì chúng tôi không thể thất bại để thực hiện các destructor.
Cho đến nay, rất tốt. Nhưng tôi đã xem qua một hành vi kỳ lạ khi tôi cố gắng để giới thiệu một mẫu nhà xây dựng trong một lớp học tương tự Bar
(mã đầy đủ, xin vui lòng thử nó cho mình):
// 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());
}
Khi biên dịch ví dụ này với Visual Studio 2005 SP1, tôi nhận được một lỗi với Bar
nhưng không phải với 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'
tôi sẽ thử điều này với một gcc gần đây càng sớm càng tôi về nhà.
Tôi không hiểu điều gì đang xảy ra: tại điểm mà hàm hủy được xác định (nghĩa là trong Bar.cpp
), định nghĩa là Bar::Impl
khả dụng, do đó không có bất kỳ vấn đề gì. Tại sao tính năng này hoạt động với Foo
và không hoạt động với Bar
?
Tôi thiếu gì ở đây?
OK, tôi thấy những gì tôi đã bỏ lỡ. Câu hỏi đặt ra là một sự đơn giản hóa đồ chơi của một vấn đề thế giới thực đã xảy ra với tôi hôm nay. Lớp con trỏ thực tế được sử dụng là 'boost :: shared_ptr', do đó tôi không thể có một con trỏ trống ở đây. Tôi sẽ điều tra bằng cách sử dụng một deleter tùy chỉnh. Cảm ơn. –
Tôi không đồng ý với ít hữu ích hơn. ** Bởi vì ** của việc sử dụng một 'scoped_ptr' vấn đề được đánh dấu bởi trình biên dịch nếu bạn quên viết destructor, cũng là cơ thể destructor cực kỳ đơn giản (trống ...). Mặt khác, sử dụng một con trỏ thô bạn sẽ không được thông báo và sẽ phải thực hiện cuộc gọi. Ngoài ra, bằng cách sử dụng 'shared_ptr', nhờ vào deleter nhúng, thực sự giúp bạn thoát khỏi gánh nặng của việc viết một destructor. –
@Matthieu: Đó là vấn đề về hương vị, tôi thích mã đơn giản và mạnh mẽ, không cần thiết chỉ bao gồm cho sự thoải mái của nó. –