为什么没有 unique_ptr::operator*() 的安全替代方案?
Why is there no safe alternative to unique_ptr::operator*()?
std::vector
具有成员函数 at()
作为 operator[]
的安全替代方法,因此应用绑定检查并且不会创建悬空引用:
void foo(std::vector<int> const&x)
{
const auto&a=x[0]; // What if x.empty()? Undefined behavior!
const auto&a=x.at(0); // Throws exception if x.empty().
}
然而,std::unique_ptr
缺少相应的功能:
void foo(std::unique_ptr<int> const&x)
{
const auto&a=*x; // What if bool(x)==false? Undefined behavior!
}
如果 std::unique_ptr
有这样一个安全的替代方案,那就太好了,比如成员 ref()
(和 cref()
)从来没有 returns 悬空引用,而是抛出异常。可能的实现:
template<typename T>
typename add_lvalue_reference<T>::type
unique_ptr<T>::ref() const noexcept(false)
{
if(bool(*this)==false)
throw run_time_error("trying to de-refrence null unique_ptr");
return this->operator*();
}
标准不提供此类内容有什么充分的理由吗?
你有
operator bool()
示例来自:
cplusplusreference
// example of unique_ptr::operator bool
#include <iostream>
#include <memory>
int main () {
std::unique_ptr<int> foo;
std::unique_ptr<int> bar (new int(12));
if (foo) std::cout << "foo points to " << *foo << '\n';
else std::cout << "foo is empty\n";
if (bar) std::cout << "bar points to " << *bar << '\n';
else std::cout << "bar is empty\n";
return 0;
}
unique_ptr 是对原始指针的简单包装,当您可以轻松检查布尔条件时无需抛出异常。
编辑:
显然 operator* 可以抛出。
Exceptions
1) may throw, e.g. if pointer defines a throwing operator*
也许有人可以阐明 hot 以定义投掷运算符*
unique_ptr
专门设计为具有空状态检测功能的轻量级指针 class(例如 optional in A proposal to add a utility class to represent optional objects (Revision 3) 中所述)
也就是说,您要求的功能已经到位,因为 operator* 文档指出:
// may throw, e.g. if pointer defines a throwing operator*
typename std::add_lvalue_reference<T>::type operator*() const;
pointer
类型is defined as
std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*
因此,通过您的自定义删除器,您可以执行任何即时操作,包括空指针检查和异常抛出
#include <iostream>
#include <memory>
struct Foo { // object to manage
Foo() { std::cout << "Foo ctor\n"; }
Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
~Foo() { std::cout << "~Foo dtor\n"; }
};
struct Exception {};
struct InternalPtr {
Foo *ptr = nullptr;
InternalPtr(Foo *p) : ptr(p) {}
InternalPtr() = default;
Foo& operator*() const {
std::cout << "Checking for a null pointer.." << std::endl;
if(ptr == nullptr)
throw Exception();
return *ptr;
}
bool operator != (Foo *p) {
if(p != ptr)
return false;
else
return true;
}
void cleanup() {
if(ptr != nullptr)
delete ptr;
}
};
struct D { // deleter
using pointer = InternalPtr;
D() {};
D(const D&) { std::cout << "D copy ctor\n"; }
D(D&) { std::cout << "D non-const copy ctor\n";}
D(D&&) { std::cout << "D move ctor \n"; }
void operator()(InternalPtr& p) const {
std::cout << "D is deleting a Foo\n";
p.cleanup();
};
};
int main()
{
std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved
try {
auto& e = *up;
} catch(Exception&) {
std::cout << "null pointer exception detected" << std::endl;
}
}
为了完整起见,我将 post 另外两个 alternatives/workarounds:
通过 operator bool
检查 unique_ptr
的指针
#include <iostream>
#include <memory>
int main()
{
std::unique_ptr<int> ptr(new int(42));
if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n';
ptr.reset();
if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n';
}
(这可能是处理这个问题最巧妙的方法)
另一种解决方案,虽然比较混乱,is to use a wrapper type which takes care of the exception handling
智能指针 API 设计的主要目标之一是成为具有附加值的直接替代品,没有陷阱或副作用,并且开销接近于零。 if (ptr) ptr->...
是通常如何安全地访问裸指针,相同的语法与智能指针配合得很好,因此当一个被另一个替换时不需要更改代码。
在指针中进行额外的有效性检查(例如,抛出异常)会干扰分支预测器,因此可能会对性能产生连锁反应,这可能不被视为零成本下降-更换了。
我不能说,为什么委员会决定不添加安全的取消引用方法 - 答案可能是 "because it wasn't proposed" 或 "because a raw pointer hasn't one either"。但是自己编写一个自由函数模板是微不足道的,它将任何指针作为参数,将其与 nullptr 进行比较,然后抛出异常或 returns 指向对象的引用。
如果您不通过指向基 class 的指针删除它,甚至可以从 unique_ptr
公开派生并添加这样一个成员函数。
但是请记住,在任何地方使用这种经过检查的方法都可能会导致严重的性能损失(与 at 相同)。通常您希望最多验证一次参数,因此在开头使用单个 if 语句更适合。
也有学校说你不应该抛出异常来响应编程错误。也许负责设计的人 unique_ptr
属于这个学校,而设计 vector 的人(更老)不属于这个学校。
我怀疑真正的答案很简单,而且很多 "Why isn't C++ like this?" 问题的答案都是一样的:
没有人提出。
std::vector
和std::unique_ptr
不是同一个人在同一时间设计的,使用方式也不同,所以不一定遵循相同的设计原则。
根据 MikeMB 的建议,这里有一个可能的自由函数实现,用于取消引用指针和 unique_ptr
s 类似的东西。
template<typename T>
inline T& dereference(T* ptr) noexcept(false)
{
if(!ptr) throw std::runtime_error("attempt to dereference a nullptr");
return *ptr;
}
template<typename T>
inline T& dereference(std::unique_ptr<T> const& ptr) noexcept(false)
{
if(!ptr) throw std::runtime_error("attempt to dereference an empty unique_ptr)");
return *ptr;
}
std::vector
具有成员函数 at()
作为 operator[]
的安全替代方法,因此应用绑定检查并且不会创建悬空引用:
void foo(std::vector<int> const&x)
{
const auto&a=x[0]; // What if x.empty()? Undefined behavior!
const auto&a=x.at(0); // Throws exception if x.empty().
}
然而,std::unique_ptr
缺少相应的功能:
void foo(std::unique_ptr<int> const&x)
{
const auto&a=*x; // What if bool(x)==false? Undefined behavior!
}
如果 std::unique_ptr
有这样一个安全的替代方案,那就太好了,比如成员 ref()
(和 cref()
)从来没有 returns 悬空引用,而是抛出异常。可能的实现:
template<typename T>
typename add_lvalue_reference<T>::type
unique_ptr<T>::ref() const noexcept(false)
{
if(bool(*this)==false)
throw run_time_error("trying to de-refrence null unique_ptr");
return this->operator*();
}
标准不提供此类内容有什么充分的理由吗?
你有
operator bool()
示例来自: cplusplusreference
// example of unique_ptr::operator bool
#include <iostream>
#include <memory>
int main () {
std::unique_ptr<int> foo;
std::unique_ptr<int> bar (new int(12));
if (foo) std::cout << "foo points to " << *foo << '\n';
else std::cout << "foo is empty\n";
if (bar) std::cout << "bar points to " << *bar << '\n';
else std::cout << "bar is empty\n";
return 0;
}
unique_ptr 是对原始指针的简单包装,当您可以轻松检查布尔条件时无需抛出异常。
编辑: 显然 operator* 可以抛出。
Exceptions 1) may throw, e.g. if pointer defines a throwing operator*
也许有人可以阐明 hot 以定义投掷运算符*
unique_ptr
专门设计为具有空状态检测功能的轻量级指针 class(例如 optional in A proposal to add a utility class to represent optional objects (Revision 3) 中所述)
也就是说,您要求的功能已经到位,因为 operator* 文档指出:
// may throw, e.g. if pointer defines a throwing operator*
typename std::add_lvalue_reference<T>::type operator*() const;
pointer
类型is defined as
std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*
因此,通过您的自定义删除器,您可以执行任何即时操作,包括空指针检查和异常抛出
#include <iostream>
#include <memory>
struct Foo { // object to manage
Foo() { std::cout << "Foo ctor\n"; }
Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
~Foo() { std::cout << "~Foo dtor\n"; }
};
struct Exception {};
struct InternalPtr {
Foo *ptr = nullptr;
InternalPtr(Foo *p) : ptr(p) {}
InternalPtr() = default;
Foo& operator*() const {
std::cout << "Checking for a null pointer.." << std::endl;
if(ptr == nullptr)
throw Exception();
return *ptr;
}
bool operator != (Foo *p) {
if(p != ptr)
return false;
else
return true;
}
void cleanup() {
if(ptr != nullptr)
delete ptr;
}
};
struct D { // deleter
using pointer = InternalPtr;
D() {};
D(const D&) { std::cout << "D copy ctor\n"; }
D(D&) { std::cout << "D non-const copy ctor\n";}
D(D&&) { std::cout << "D move ctor \n"; }
void operator()(InternalPtr& p) const {
std::cout << "D is deleting a Foo\n";
p.cleanup();
};
};
int main()
{
std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved
try {
auto& e = *up;
} catch(Exception&) {
std::cout << "null pointer exception detected" << std::endl;
}
}
为了完整起见,我将 post 另外两个 alternatives/workarounds:
通过
检查operator bool
unique_ptr
的指针#include <iostream> #include <memory> int main() { std::unique_ptr<int> ptr(new int(42)); if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n'; ptr.reset(); if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n'; }
(这可能是处理这个问题最巧妙的方法)
另一种解决方案,虽然比较混乱,is to use a wrapper type which takes care of the exception handling
智能指针 API 设计的主要目标之一是成为具有附加值的直接替代品,没有陷阱或副作用,并且开销接近于零。 if (ptr) ptr->...
是通常如何安全地访问裸指针,相同的语法与智能指针配合得很好,因此当一个被另一个替换时不需要更改代码。
在指针中进行额外的有效性检查(例如,抛出异常)会干扰分支预测器,因此可能会对性能产生连锁反应,这可能不被视为零成本下降-更换了。
我不能说,为什么委员会决定不添加安全的取消引用方法 - 答案可能是 "because it wasn't proposed" 或 "because a raw pointer hasn't one either"。但是自己编写一个自由函数模板是微不足道的,它将任何指针作为参数,将其与 nullptr 进行比较,然后抛出异常或 returns 指向对象的引用。
如果您不通过指向基 class 的指针删除它,甚至可以从 unique_ptr
公开派生并添加这样一个成员函数。
但是请记住,在任何地方使用这种经过检查的方法都可能会导致严重的性能损失(与 at 相同)。通常您希望最多验证一次参数,因此在开头使用单个 if 语句更适合。
也有学校说你不应该抛出异常来响应编程错误。也许负责设计的人 unique_ptr
属于这个学校,而设计 vector 的人(更老)不属于这个学校。
我怀疑真正的答案很简单,而且很多 "Why isn't C++ like this?" 问题的答案都是一样的:
没有人提出。
std::vector
和std::unique_ptr
不是同一个人在同一时间设计的,使用方式也不同,所以不一定遵循相同的设计原则。
根据 MikeMB 的建议,这里有一个可能的自由函数实现,用于取消引用指针和 unique_ptr
s 类似的东西。
template<typename T>
inline T& dereference(T* ptr) noexcept(false)
{
if(!ptr) throw std::runtime_error("attempt to dereference a nullptr");
return *ptr;
}
template<typename T>
inline T& dereference(std::unique_ptr<T> const& ptr) noexcept(false)
{
if(!ptr) throw std::runtime_error("attempt to dereference an empty unique_ptr)");
return *ptr;
}