`std::unique_ptr` 的这种行为是预期的吗?
Is this behavior of `std::unique_ptr` expected?
我有一个我一直在做的项目,它使用多态性通过覆盖 encode
函数来实现对象的编码。
现在,这个项目一直在使用指针来实现多态行为。由于我的应用程序相对较新,因此我认为从常规原始指针切换到智能指针不会太困难,因为它们会自动管理内存。
在我的一个函数中,我 return 一个 std::unique_ptr
。在 returning 智能指针之后,我立即想要强制转换底层原始指针。因此,对正在发生的事情的过度简化看起来像这样:
std::unique_ptr<something> do_something() {
return std::unique_ptr<something>(new something());
}
void caller() {
auto output = static_cast<another*>(do_something().get());
// Do something with output...
}
我知道 std::unique_ptr
没有复制构造函数,因为它在任何给定时刻只能 "owned" 一次。实现复制构造函数将允许创建重复指针,这与此原则背道而驰。如果我的理解是正确的,下面的代码使用移动构造函数初始化 B
,并且不复制 A
:
std::unique_ptr<int> get_a() {
std::unique_ptr<int> A(new int);
return A;
}
void get_b() {
auto B = get_a();
}
这正是我想要的行为。但是,在我的示例中,程序大约有一半的时间会崩溃。另一半时间,它 似乎 可以工作,但底层指针的内容只是普通的垃圾。 (我怀疑这是未定义的行为,并且 get()
只是有时 returning 一个可读地址。)
现在,经过一番思考,我得出结论,如果 B
确实是通过移动构造函数初始化的,那么 B
本身必须 而不是 是临时的。但是,在我的第一个示例中,do_something()
的结果是临时的,我正在 移动构建它 !所以我决定停止内联对 do_something()
的调用,然后我立即得到了预期的行为!每次我在临时对象上使用 get()
时,结果都是不可预测的,而且完全是错误的。
我的问题是:
我应该期待 unique_ptr
的这种行为吗?如果是这样,如何记录这种行为? (更具体地说,我的第一个示例是未定义的行为吗?)是否有任何替代方法(shared_ptr
,也许?)可以让我内联函数调用并仍然获得预期的结果?
这里的关键在你的语句中总结了"they would automatically manage memory".
你的 unique_ptr
(post-move) 是一个临时的,在声明 output
之后立即超出范围,使用动态分配的 int
它并留下 output
悬空。
暂时的。 临时!如此命名是有充分理由的……
这种行为在很多很多地方都是 "documented":存储指向超出范围的对象的指针是一个非常糟糕的主意,从今以后 使用 该指针完全损坏(是的,它是 UB)。
你不能通过切换到其他智能指针来解决这个问题,不:停止 "inlining" 函数调用(并停止使用这个术语;那不是 "inlining" 的意思)并确保只要你想通过指针访问它,对象就存在。
auto ptr = do_something();
auto output = static_cast<another*>(ptr.get());
我有一个我一直在做的项目,它使用多态性通过覆盖 encode
函数来实现对象的编码。
现在,这个项目一直在使用指针来实现多态行为。由于我的应用程序相对较新,因此我认为从常规原始指针切换到智能指针不会太困难,因为它们会自动管理内存。
在我的一个函数中,我 return 一个 std::unique_ptr
。在 returning 智能指针之后,我立即想要强制转换底层原始指针。因此,对正在发生的事情的过度简化看起来像这样:
std::unique_ptr<something> do_something() {
return std::unique_ptr<something>(new something());
}
void caller() {
auto output = static_cast<another*>(do_something().get());
// Do something with output...
}
我知道 std::unique_ptr
没有复制构造函数,因为它在任何给定时刻只能 "owned" 一次。实现复制构造函数将允许创建重复指针,这与此原则背道而驰。如果我的理解是正确的,下面的代码使用移动构造函数初始化 B
,并且不复制 A
:
std::unique_ptr<int> get_a() {
std::unique_ptr<int> A(new int);
return A;
}
void get_b() {
auto B = get_a();
}
这正是我想要的行为。但是,在我的示例中,程序大约有一半的时间会崩溃。另一半时间,它 似乎 可以工作,但底层指针的内容只是普通的垃圾。 (我怀疑这是未定义的行为,并且 get()
只是有时 returning 一个可读地址。)
现在,经过一番思考,我得出结论,如果 B
确实是通过移动构造函数初始化的,那么 B
本身必须 而不是 是临时的。但是,在我的第一个示例中,do_something()
的结果是临时的,我正在 移动构建它 !所以我决定停止内联对 do_something()
的调用,然后我立即得到了预期的行为!每次我在临时对象上使用 get()
时,结果都是不可预测的,而且完全是错误的。
我的问题是:
我应该期待 unique_ptr
的这种行为吗?如果是这样,如何记录这种行为? (更具体地说,我的第一个示例是未定义的行为吗?)是否有任何替代方法(shared_ptr
,也许?)可以让我内联函数调用并仍然获得预期的结果?
这里的关键在你的语句中总结了"they would automatically manage memory".
你的 unique_ptr
(post-move) 是一个临时的,在声明 output
之后立即超出范围,使用动态分配的 int
它并留下 output
悬空。
暂时的。 临时!如此命名是有充分理由的……
这种行为在很多很多地方都是 "documented":存储指向超出范围的对象的指针是一个非常糟糕的主意,从今以后 使用 该指针完全损坏(是的,它是 UB)。
你不能通过切换到其他智能指针来解决这个问题,不:停止 "inlining" 函数调用(并停止使用这个术语;那不是 "inlining" 的意思)并确保只要你想通过指针访问它,对象就存在。
auto ptr = do_something();
auto output = static_cast<another*>(ptr.get());