临时数据成员的寿命延长和 API 设计
Lifetime extension of temporaries' data members and API design
假设我有一个跨平台的Path
类,如:
class Path {
public:
// ...
Path parent() const; // e.g., /foo/bar -> /foo
std::string const& as_utf8() const {
return path;
}
private:
std::string path;
};
parent()
成员函数返回this
路径的父路径,因此它(正确地)返回一个新构造的Path
对象来表示它。
对于将操作系统级别的路径表示为UTF-8字符串的平台(例如Unix), as_utf8()
直接返回对内部表示path
的引用似乎是合理的,因为它已经是 UTF-8。
std::string const &s = my_path.as_utf8(); // OK (as long as my_path exists)
// ...
Path const &parent = my_path.parent(); // OK (temporary lifetime extended)
这两行都可以,因为:
- 假设
my_path
存在,则s
仍然有效。 -
parent()
返回的临时对象的生存期由const&
延长。
到目前为止,一切顺利。但是,如果我有如下代码:
std::string const &s = my_path.parent().as_utf8(); // WRONG
则是错误的,因为parent()
返回的临时对象没有延长其生存期,因为const&
没有指向临时对象,而是指向它的数据成员。此时,如果您尝试使用s
,您将获得垃圾或核心转储。如果代码改为:
std::string as_utf8() const { // Note: object and NOT const&
return path;
}
则代码是正确的。但是,每次调用这个成员函数时都创建一个临时对象是低效的。这也意味着没有"getter"成员函数应该永远返回对其数据成员的引用。
如果API保持原样,那么它似乎给调用者带来了不必要的负担,必须查看as_utf8()
的返回类型,以查看它是否返回const&
:如果是,那么调用者必须使用对象,而不能使用const&
;如果它返回一个对象,那么调用者可以使用const&
。
那么有没有办法解决这个问题,使API在大多数情况下都是高效的,但防止用户从看似无害的代码中获得悬空引用?
顺便说一下,这是用g++ 5.3编译的。有可能临时的生存期应该被延长,但是编译器有一个错误。
您可以做的是创建两个不同版本的as_utf8()
,一个用于左值,一个用于右值。不过你需要c++ 11。
这样,您就可以两全其美:当对象不是临时对象时,可以使用const&
;当对象不是临时对象时,可以使用有效的移动。
std::string const& as_utf8() const & {
// ^^^ Called from lvalues only
return path;
}
std::string as_utf8() const && {
// ^^^^ Called from rvalues only
return std::move(path); //We don't need path any more
}
在我看来,关于是返回引用还是返回对象的指导原则是检查原始类的定义角色。
。该方法是否暴露了一个简单的属性(用于引用,特别是如果它是不可变的),或者它是否生成什么?
如果它生成一个新的对象或表示,我们可以合理地期望它返回一个不同的对象。
api的用户通常习惯于理解属性不会比它们的宿主对象更长寿。这当然可以在文档中明确说明。
。
struct path
{
/// a property
/// @note lifetime is no longer than the lifetime of this object
std::string const& native() const;
/// generate a new string representation in a different format
std::string to_url() const;
};
在这种情况下,我个人会避免使用as_
的前缀,因为对我来说,它暗示我们返回同一对象的新表示,例如:
struct world
: std::enable_shared_from_this<world>
{
struct sky {} my_sky_;
/// returns a shared_ptr to my sky object, which shares its lifetime
/// with this world.
std::shared_ptr<sky> as_sky()
{
return std::shared_ptr<sky>(shared_from_this(), std::addressof(my_sky_));
}
};
- 用于从可能不存在的容器中检索对象的 API 设计
- C API设计:正在使用void*一个坏主意
- 在 API 设计中,我什么时候应该更喜欢长度为 2 std::tuple 而不是一对
- 为瘦客户端设计客户端(厚)/服务器+额外的REST API
- 一个包阅读器Qt/C++API设计
- API 设计是告诉 API 的使用者分配内存的最佳做法
- C/C++ API 设计困境
- C++ 游戏设计 - 什么库/API
- 客户端 API 的make_unique、工厂方法或不同设计
- API在C++中的设计
- 为可变函数设计一个更好的API
- API工厂模式设计缺陷
- C++API设计和错误处理
- C++ API 设计中的得墨忒耳定律
- 如何在设计API时设计带有const和volatile的重载成员函数
- 如何在公共 API 接口类中将 auto getter&setter 与 PIMPL 设计模式相结合
- 图形库API设计
- c++库API设计问题
- 临时数据成员的寿命延长和 API 设计
- C++ API 设计:清理公共接口