2011-02-07 23 views
5

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?

Trả lời

5

Đây là hàm hủy của boost::shared_ptr<> yêu cầu đối tượng phải hoàn thành khi sử dụng dấu phân cách boost::checked_deleter<>. Bởi vì bạn đặt hàm tạo phạm vi Bar::Bar(I begin, I end) trong tệp tiêu đề trình biên dịch phải tạo mã phá hủy các thành viên đã xây dựng nếu trình xây dựng của bạn ném, do đó nó đang cố gắng khởi tạo boost::scoped_ptr<T>::~scoped_ptr(void) khi khởi tạo trình tạo mẫu này.

Nó ít hữu dụng hơn khi sử dụng con trỏ thông minh với pimpl. Vì bạn thường cần phải cung cấp một destructor anyway, bạn cũng có thể đặt delete pimpl trong đó destructor và được thực hiện với điều đó.

+0

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. –

+3

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. –

+0

@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ó. –

1

Từ Boost Boost documentation:

Lưu ý rằng scoped_ptr đòi hỏi rằng T là một loại hoàn chỉnh vào thời điểm hủy diệt, nhưng shared_ptr không.

Chuyển sang shared_ptr và tất cả sẽ tốt - không cần phải có trình phá hủy (trống hoặc cách khác). Nếu bạn muốn làm cho lớp không thể sao chép được vì vậy nó có ngữ nghĩa bạn đã có từ scoped_ptr, kế thừa (riêng) từ boost :: noncopyable.

Các vấn đề liên quan