使用 std::unique_ptr / std::shared_ptr const 正确组合
const correct composition using std::unique_ptr / std::shared_ptr
目前我想知道如何正确使用 std::unique_ptr
作为关于 const 正确性的成员变量。
以下示例允许更改 my_foo
拥有的内容,尽管它是常量:
#include <iostream>
#include <memory>
struct foo {
foo() : value_ptr_(std::make_unique<int>(3)) {}
void increment() const {
++(*value_ptr_);
}
int get_value() const {
return *value_ptr_;
}
std::unique_ptr<int> value_ptr_;
};
int main() {
const foo my_foo;
std::cout << my_foo.get_value() << std::endl;
my_foo.increment(); // But my_foo is const!
std::cout << my_foo.get_value() << std::endl;
}
用 std::make_unique<const T>
替换 std::make_unique<T>
乍一看似乎是个不错的解决方案。但是,这不允许更改 my_foo
的内容,即使它是非常量:
#include <iostream>
#include <memory>
struct foo {
foo() : value_ptr_(std::make_unique<int>(3)) {}
void increment() {
++(*value_ptr_);
}
int get_value() const {
return *value_ptr_;
}
std::unique_ptr<const int> value_ptr_;
};
int main() {
foo my_foo;
std::cout << my_foo.get_value() << std::endl;
my_foo.increment(); // compiler error
std::cout << my_foo.get_value() << std::endl;
}
在这个最小的例子中有一个指向 int 的指针当然不是很有意义,但在实际代码中 unique_ptr
可以保存一个指向多态的基 class 的指针,即我们无法简单地按值存储的对象。
那么如何更好地处理这种情况?
我这样做的方法是提供一个内部协议,以提供对底层实现的正确引用的访问。
像这样:
struct foo {
// standard (in your codebase) protocol to express the impl base class
using underlying_impl = int;
// standard protocol to express ownership semantics
using implementation_handle = std::unique_ptr<underlying_impl>;
// construction via private 'construct' protocol
foo() : value_ptr_(construct(3)) {}
// all internal access to the implementation via a the protocol
// of get_impl()
auto operator++() -> foo&
{
// not-const - compiles fine
++get_impl();
return *this;
}
void increment() const {
// now won't compile - get_impl() propagates const correctly
// ++get_impl();
}
private:
static auto construct(int val) -> implementation_handle
{
return std::make_unique<underlying_impl>(val);
}
// two versions of get_impl() - const and mutable
auto get_impl() const -> underlying_impl const&
{
return *value_ptr_;
}
auto get_impl() -> underlying_impl&
{
return *value_ptr_;
}
// actual storage of the implementation handle
implementation_handle value_ptr_;
};
您可以继承 std::unique_ptr
并仅覆盖 3 个(unique_ptr<T[]>
为 4 个)方法,提供 const/non-const 重载:
template <typename T>
struct propagating_unique_ptr : std::unique_ptr<T> {
using unique_ptr<T>::unique_ptr;
using unique_ptr<T>::operator =;
const T *get() const noexcept {
return unique_ptr<T>::get();
}
T *get() noexcept {
return unique_ptr<T>::get();
}
const T &operator *() const noexcept {
return unique_ptr<T>::operator *();
}
T &operator *() noexcept {
return unique_ptr<T>::operator *();
}
const T *operator -> () const noexcept {
return unique_ptr<T>::get();
}
T *operator -> () noexcept {
return unique_ptr<T>::get();
}
};
目前我想知道如何正确使用 std::unique_ptr
作为关于 const 正确性的成员变量。
以下示例允许更改 my_foo
拥有的内容,尽管它是常量:
#include <iostream>
#include <memory>
struct foo {
foo() : value_ptr_(std::make_unique<int>(3)) {}
void increment() const {
++(*value_ptr_);
}
int get_value() const {
return *value_ptr_;
}
std::unique_ptr<int> value_ptr_;
};
int main() {
const foo my_foo;
std::cout << my_foo.get_value() << std::endl;
my_foo.increment(); // But my_foo is const!
std::cout << my_foo.get_value() << std::endl;
}
用 std::make_unique<const T>
替换 std::make_unique<T>
乍一看似乎是个不错的解决方案。但是,这不允许更改 my_foo
的内容,即使它是非常量:
#include <iostream>
#include <memory>
struct foo {
foo() : value_ptr_(std::make_unique<int>(3)) {}
void increment() {
++(*value_ptr_);
}
int get_value() const {
return *value_ptr_;
}
std::unique_ptr<const int> value_ptr_;
};
int main() {
foo my_foo;
std::cout << my_foo.get_value() << std::endl;
my_foo.increment(); // compiler error
std::cout << my_foo.get_value() << std::endl;
}
在这个最小的例子中有一个指向 int 的指针当然不是很有意义,但在实际代码中 unique_ptr
可以保存一个指向多态的基 class 的指针,即我们无法简单地按值存储的对象。
那么如何更好地处理这种情况?
我这样做的方法是提供一个内部协议,以提供对底层实现的正确引用的访问。
像这样:
struct foo {
// standard (in your codebase) protocol to express the impl base class
using underlying_impl = int;
// standard protocol to express ownership semantics
using implementation_handle = std::unique_ptr<underlying_impl>;
// construction via private 'construct' protocol
foo() : value_ptr_(construct(3)) {}
// all internal access to the implementation via a the protocol
// of get_impl()
auto operator++() -> foo&
{
// not-const - compiles fine
++get_impl();
return *this;
}
void increment() const {
// now won't compile - get_impl() propagates const correctly
// ++get_impl();
}
private:
static auto construct(int val) -> implementation_handle
{
return std::make_unique<underlying_impl>(val);
}
// two versions of get_impl() - const and mutable
auto get_impl() const -> underlying_impl const&
{
return *value_ptr_;
}
auto get_impl() -> underlying_impl&
{
return *value_ptr_;
}
// actual storage of the implementation handle
implementation_handle value_ptr_;
};
您可以继承 std::unique_ptr
并仅覆盖 3 个(unique_ptr<T[]>
为 4 个)方法,提供 const/non-const 重载:
template <typename T>
struct propagating_unique_ptr : std::unique_ptr<T> {
using unique_ptr<T>::unique_ptr;
using unique_ptr<T>::operator =;
const T *get() const noexcept {
return unique_ptr<T>::get();
}
T *get() noexcept {
return unique_ptr<T>::get();
}
const T &operator *() const noexcept {
return unique_ptr<T>::operator *();
}
T &operator *() noexcept {
return unique_ptr<T>::operator *();
}
const T *operator -> () const noexcept {
return unique_ptr<T>::get();
}
T *operator -> () noexcept {
return unique_ptr<T>::get();
}
};