2011-01-18 8 views
22

Thường khá khó hiểu đối với người mới sử dụng C++ mà hàm thành viên được phép gọi các phương thức không phải const trên các đối tượng được tham chiếu bởi lớp (hoặc bằng con trỏ hoặc tham chiếu). Ví dụ, sau đây là hoàn toàn chính xác:Tuyên truyền constness thành dữ liệu được chỉ định bởi các biến thành viên

class SomeClass 
{ 
    class SomeClassImpl; 
    SomeClassImpl * impl_; // PImpl idiom 

    public:  

    void const_method() const; 
}; 

struct SomeClass::SomeClassImpl 
{ 
    void non_const_method() { /*modify data*/ } 
}; 

void SomeClass::const_method() const 
{ 
    impl_->non_const_method(); //ok because impl_ is const, not *impl_ 
}; 

Tuy nhiên, đôi khi nó sẽ là khá tiện dụng nếu constness sẽ tuyên truyền để các vật nhọn (Tôi tự nguyện sử dụng thành ngữ pImpl bởi vì nó là một trong những trường hợp mà tôi nghĩ " constness tuyên truyền "sẽ rất hữu ích).

Khi sử dụng con trỏ, điều này một cách dễ dàng có thể đạt được bằng cách sử dụng một số loại con trỏ thông minh với các nhà khai thác quá tải trên constness:

template < typename T > 
class const_propagating_ptr 
{ 
    public: 

    const_propagating_ptr(T * ptr) : ptr_(ptr) {} 

    T  & operator*()  { return *ptr_; } 
    T const & operator*() const { return *ptr_; } 

    T  * operator->()  { return ptr_; } 
    T const * operator->() const { return ptr_; } 

    // assignment operator (?), get() method (?), reset() method (?) 
    // ... 

    private: 

    T * ptr_; 
}; 

Bây giờ, tôi chỉ cần phải sửa đổi SomeClass::impl_ là một const_propagating_ptr<SomeClassImpl> để có được những hành vi truy nã .

Vì vậy, tôi có một vài câu hỏi về vấn đề này:

  1. Có một số vấn đề với công tác tuyên truyền constness mà tôi đã bỏ qua?
  2. Nếu không, có thư viện nào cung cấp các lớp học để có được tuyên truyền về độ chói không? Sẽ không hữu ích nếu các con trỏ thông minh thông thường (unique_ptr, shared_ptr, v.v.) cung cấp một số ý nghĩa để có được hành vi này (ví dụ thông qua một tham số mẫu)?
+3

Nếu tôi chỉ sao chép con trỏ thông minh thì sao? Thì đấy, tôi có một cái không phải. –

+1

'T const * const toán tử ->() const {return ptr_; } '- có lẽ không cần đến' const' thứ hai ở đây –

+0

@Alf và @robin: Bản phác thảo mà tôi đưa ra có thể thực hiện có thể được rải rác với các lỗi (mặc dù kích thước ngắn của nó :)), nó không phải là điểm trung tâm của câu hỏi. Tuy nhiên, phản hồi của bạn thực sự được đánh giá cao! Về vấn đề sao chép, tôi không thấy tại thời điểm này, chúng ta có thể ngăn chặn điều đó bằng cách nào, nhưng thường thì bạn không thể ngăn mình hoàn toàn tự bắn mình vào chân (ví dụ, bạn luôn có thể 'const_cast' đi lệch, nó không có nghĩa là const là vô ích). Liên quan đến bình luận thứ hai, bạn đúng @robin, tôi đã làm điều đó để ngăn chặn 'ptr_' khỏi bị ... –

Trả lời

2
  1. Như @Alf P. Steinbach ghi chú khác, bạn giám sát thực tế là sao chép con trỏ của bạn sẽ mang lại một đối tượng không const trỏ đến các đối tượng cơ bản giống nhau. Pimpl (bên dưới) độc đáo phá vỡ vấn đề bằng cách thực hiện một bản sao sâu, unique_ptr phá vỡ nó bằng cách không thể sao chép được. Nó dễ dàng hơn nhiều, tất nhiên, nếu con trỏ được sở hữu bởi một thực thể duy nhất.

  2. Boost.Optional tuyên truyền const-ness, tuy nhiên nó không chính xác là một con trỏ (mặc dù nó mô hình khái niệm OptionalPointee). Tôi biết không có thư viện nào khác.

  3. Tôi ưu tiên rằng họ cung cấp theo mặc định. Việc thêm một tham số mẫu khác (đặc điểm lớp tôi đoán) dường như không đáng để gây rắc rối. Tuy nhiên điều đó sẽ thay đổi hoàn toàn cú pháp từ một con trỏ cổ điển, vì vậy tôi không chắc chắn rằng mọi người sẽ sẵn sàng nắm lấy nó.


Mã của lớp

template <class T> 
class Pimpl 
{ 
public: 
    /** 
    * Types 
    */ 
    typedef T value; 
    typedef const T const_value; 
    typedef T* pointer; 
    typedef const T* const_pointer; 
    typedef T& reference; 
    typedef const T& const_reference; 

    /** 
    * Gang of Four 
    */ 
    Pimpl() : _value(new T()) {} 
    explicit Pimpl(const_reference v) : _value(new T(v)) {} 

    Pimpl(const Pimpl& rhs) : _value(new T(*(rhs._value))) {} 

    Pimpl& operator=(const Pimpl& rhs) 
    { 
    Pimpl tmp(rhs); 
    swap(tmp); 
    return *this; 
    } // operator= 

    ~Pimpl() { boost::checked_delete(_value); } 

    void swap(Pimpl& rhs) 
    { 
    pointer temp(rhs._value); 
    rhs._value = _value; 
    _value = temp; 
    } // swap 

    /** 
    * Data access 
    */ 
    pointer get() { return _value; } 
    const_pointer get() const { return _value; } 

    reference operator*() { return *_value; } 
    const_reference operator*() const { return *_value; } 

    pointer operator->() { return _value; } 
    const_pointer operator->() const { return _value; } 

private: 
    pointer _value; 
}; // class Pimpl<T> 

// Swap 
template <class T> 
void swap(Pimpl<T>& lhs, Pimpl<T>& rhs) { lhs.swap(rhs); } 

// Not to be used with pointers or references 
template <class T> class Pimpl<T*> {}; 
template <class T> class Pimpl<T&> {}; 
2

Một cách tiếp cận Pimpl là chỉ cần không sử dụng con trỏ trực tiếp ngoại trừ thông qua hai chức năng accessor.

class SomeClass 
{ 
    private: 
    class SomeClassImpl; 
    SomeClassImpl * impl_; // PImpl idiom - don't use me directly! 

    SomeClassImpl * mutable_impl() { return impl_; } 
    const SomeClassImpl * impl() const { return impl_; } 

    public:  

    void const_method() const 
    { 
     //Can't use mutable_impl here. 
     impl()->const_method(); 
    } 
    void non_const_method() const 
    { 
     //Here I can use mutable_impl 
     mutable_impl()->non_const_method(); 
    } 
}; 
+0

Đây là giải pháp tôi ban đầu đã cố gắng, ngoại trừ việc tôi đã sử dụng một cá thể lớp lồng nhau để đóng gói con trỏ, để ngăn chặn truy cập của nó từ lớp bên ngoài (chỉ các getters có quyền truy cập nhờ khai báo bạn bè). Tuy nhiên, giải pháp này cho biết thêm rất nhiều mã boilerplate cho mỗi con trỏ mà chúng ta cần tuyên truyền const. Hơn nữa, IMHO, sử dụng getters và setters giảm khả năng đọc (thuộc tính là một trong những tính năng C# yêu thích của tôi :)!). –

1

Đối với hồ sơ, tôi chỉ phát hiện ra rằng Loki library không cung cấp một const tuyên truyền con trỏ (ConstPropPtr<T>). Nó trông giống như một trong câu hỏi, ngoại trừ việc nó cũng xóa con trỏ được bọc trong destructor của nó, và nó được sử dụng để thực hiện một Pimpl class tương tự như the one proposed by @Matthieu (nhưng không thể sao chép được).

0

Nếu bạn nghĩ rằng nó sẽ "tuyên truyền" const-Ness, sau đó nó có nghĩa là bạn không thực sự tin rằng nó là một con trỏ (hoặc tham chiếu), nhưng bạn tin rằng nó là một chứa: nếu giá trị là không đổi khi đối tượng là hằng số, đó là vì đối tượng chứa giá trị.

Vì vậy, sao chép đối tượng sao chép giá trị, ít nhất là hợp lý (CoW).

Nếu bạn nhấn mạnh rằng đó là một con trỏ/tham chiếu IOW mà bạn có thể sao chép đối tượng trong khi chia sẻ giá trị chứa, thì bạn có giao diện không đối xứng (mâu thuẫn).

Kết luận: hãy quyết định. Nó là một container hoặc một con trỏ.

Con trỏ không truyền bá const-ness, theo định nghĩa.

+2

Tôi nghĩ rằng bạn đã bỏ lỡ điểm: ngay cả khi một đối tượng được chứa (sở hữu) bởi một đối tượng khác, đôi khi bạn cần lưu trữ nó bằng cách sử dụng con trỏ (đa hình, khởi tạo lười biếng, tường lửa biên dịch, ...). Trong các trường hợp này, việc truyền bá const sẽ hữu ích: vì * khái niệm * con trỏ được chứa bởi đối tượng, các phương thức const của đối tượng không được phép sửa đổi nó. Tuy nhiên, tôi đồng ý rằng lớp mẫu của tôi 'SomeClass' nên có hoặc là định nghĩa một constructor sao chép hoặc bản sao không được phép, để tránh tai nạn chia sẻ' impl_' giữa 'SomeClass' 'trường hợp. –

+0

_ "Tôi nghĩ bạn đã bỏ lỡ điểm: bạn có thể muốn truyền bá const_" Tôi hiểu điều đó và bạn đã bỏ lỡ quan điểm của mình: bạn muốn truyền bá const-ness vì bạn không muốn ** con trỏ ** ngữ nghĩa. Vui lòng không gọi lớp đó là 'something_pointer'! "_even khi một đối tượng được chứa (sở hữu) bởi một đối tượng khác, đôi khi bạn cần phải lưu trữ nó bằng cách sử dụng một con trỏ_" Có, thực sự. – curiousguy

+0

BTW, trong trường hợp ai đó cho rằng tên không quan trọng: nếu bạn gọi cái gì đó là "con trỏ" (không thực sự có con trỏ ngữ nghĩa), một số lập trình viên khác có thể đi cùng và thêm những thứ liên quan đến con trỏ có ý nghĩa đối với một cái gì đó không phải là nghĩa vụ phải có con trỏ ngữ nghĩa. Bằng cách gọi nó là một container bạn (chủ yếu) tránh nguy cơ này. – curiousguy