C++ 移动语义 - 包装遗留 C API
C++ Move Semantics - Wrapping Legacy C APIs
我正在使用遗留 C API,在这种情况下,获取某些资源非常昂贵,释放该资源绝对至关重要。我正在使用 C++14,我想创建一个 class 来管理这些资源。这是东西的基本骨架...
class Thing
{
private:
void* _legacy;
public:
void Operation1(...);
int Operation2(...);
string Operation3(...);
private:
Thing(void* legacy) :
_legacy(legacy)
{
}
};
这不是真正的单例模式。没有什么是静态的,可能有很多 Thing
个实例,它们都管理着自己的遗留资源。此外,这不仅仅是一个智能指针。包装指针 _legacy
是私有的,所有操作都通过一些 public 实例函数公开,这些实例函数对消费者隐藏了遗留 API。
构造函数是私有的,因为 Thing
的实例将从静态工厂或 命名构造函数 中 return 实际获取资源。这是该工厂的廉价模仿,使用 malloc()
作为将调用遗留 API ...
的代码的占位符
public:
static Thing Acquire()
{
// Do many things to acquire the thing via the legacy API
void* legacy = malloc(16);
// Return a constructed thing
return Thing(legacy);
}
这是负责释放遗留资源的析构函数,同样,free()
只是一个占位符...
~Thing() noexcept
{
if (nullptr != _legacy)
{
// Do many things to free the thing via the legacy API
// (BUT do not throw any exceptions!)
free(_legacy);
_legacy = nullptr;
}
}
现在,我想确保只有一个遗留资源由 Thing
的一个实例管理。我不希望 Thing
class 的消费者随意传递实例——它们必须在本地属于 class 或函数,直接或通过 unique_ptr
,或者用可以传递的 shared_ptr
包裹起来。为此,我删除了赋值运算符和拷贝构造函数...
private:
Thing(Thing const&) = delete;
void operator=(Thing const&) = delete;
然而,这增加了一个额外的挑战。要么我必须将我的工厂方法更改为 return a unique_ptr<Thing>
或 shared_ptr<Thing>
,要么我必须实现移动语义。我不想规定应该使用 Thing
的模式,所以我选择添加一个移动构造函数和移动赋值运算符,如下所示...
Thing(Thing&& old) noexcept : _legacy(old._legacy)
{
// Reset the old thing's state to reflect the move
old._legacy = nullptr;
}
Thing& operator= (Thing&& old) noexcept
{
if (&old != this)
{
swap(_legacy, old._legacy);
}
return (*this);
}
完成这一切后,我可以使用 Thing
作为本地并移动它...
Thing one = Thing::Acquire();
Thing two = move(one);
我无法通过尝试提交自我分配来打破模式:
Thing one = Thing::Acquire();
one = one; // Build error!
我也可以做一个unique_ptr
给一个...
auto three = make_unique<Thing>(Thing::Acquire());
或者 shared_ptr
...
auto three = make_shared<Thing>(Thing::Acquire());
一切都如我所料,我的析构函数 运行 在我所有的测试中都恰到好处。事实上,唯一让人恼火的是 make_unique
和 make_shared
都 实际上 调用了移动构造函数——它没有像我希望的那样被优化掉。
第一个问题:我是否正确实现了移动-构造函数和移动赋值运算符? (它们对我来说相当陌生,这将是我第一次在愤怒中使用它们。)
第二个问题:请对这个图案进行评论!这是将遗留资源包装在 C++14 class 中的好方法吗?
最后: 我是否应该更改任何内容以使代码更好、更快、更简单或更具可读性?
您应该将 Thing
包装在智能指针中,这样您就无需担心复制和移动语义。
class Thing
{
private:
void* _legacy;
public:
void Operation1(...);
int Operation2(...);
string Operation3(...);
Thing(const Thing&) = delete;
Thing(Thing&&) = delete;
Thing& operator=(const Thing&) = delete;
Thing& operator=(Thing&&) = delete;
static std::shared_ptr<Thing> acquire() {
return std::make_shared<Thing>();
}
private:
Thing() : _legacy(malloc(16)) {
// ...
}
~Thing() {
free(_legacy);
}
};
同样,你可以用unique_ptr
来做:
std::unique_ptr<Thing> acquire() {
return std::make_unique<Thing>();
}
你似乎暗示你只想拥有这个东西的一个实例,尽管即使在你的解决方案中你也没有尝试做那样的事情。为此,您需要静态变量。不过请记住,在这种情况下,您的资源只会在 main()
函数退出后才会被释放。例如:
static std::shared_ptr<Thing> acquire() {
static std::shared_ptr<Thing> instance;
if (!instance) {
instance = std::make_shared<Thing>();
}
return instance;
}
或unique_ptr
版本:
static Thing& acquire() {
static std::unique_ptr<Thing> instance;
if (!instance) {
instance = std::make_unique<Thing>();
}
return *instance;
}
或者,您可以使用weak_ptr
在程序范围内获取一个实例,当没有人使用它时释放它。在这种情况下,您将无法使用 unique_ptr
来达到此目的。如果对象被释放然后再次需要,此版本将重新创建对象。
static std::shared_ptr<Thing> acquire() {
static std::weak_ptr<Thing> instance;
if (instance.expired()) {
instance = std::make_shared<Thing>();
}
return instance.lock();
}
struct free_thing{
void operator()(void* p)const{
// stuff
free(p);
}
};
using upthing=std::unique_ptr<void,free_thing>;
upthing make_thing(stuff){
void*retval;
// code
return upthing(retval);
}
将 upthing
作为 _legacy
存储在您的 Thing
中。使用默认 dtor,移动 ctor,为 Thing
(=default
) 移动赋值。
销毁代码进入free_thing
。
你的ctor创造了东西。
现在用户可以将您的 Thing
视为仅移动值类型。
除非确实需要,否则不要编写自己的指针管理器。独特和共享为您做很多事情:如果您编写自己的智能指针,甚至可以将它们用作内部胆量。
我正在使用遗留 C API,在这种情况下,获取某些资源非常昂贵,释放该资源绝对至关重要。我正在使用 C++14,我想创建一个 class 来管理这些资源。这是东西的基本骨架...
class Thing
{
private:
void* _legacy;
public:
void Operation1(...);
int Operation2(...);
string Operation3(...);
private:
Thing(void* legacy) :
_legacy(legacy)
{
}
};
这不是真正的单例模式。没有什么是静态的,可能有很多 Thing
个实例,它们都管理着自己的遗留资源。此外,这不仅仅是一个智能指针。包装指针 _legacy
是私有的,所有操作都通过一些 public 实例函数公开,这些实例函数对消费者隐藏了遗留 API。
构造函数是私有的,因为 Thing
的实例将从静态工厂或 命名构造函数 中 return 实际获取资源。这是该工厂的廉价模仿,使用 malloc()
作为将调用遗留 API ...
public:
static Thing Acquire()
{
// Do many things to acquire the thing via the legacy API
void* legacy = malloc(16);
// Return a constructed thing
return Thing(legacy);
}
这是负责释放遗留资源的析构函数,同样,free()
只是一个占位符...
~Thing() noexcept
{
if (nullptr != _legacy)
{
// Do many things to free the thing via the legacy API
// (BUT do not throw any exceptions!)
free(_legacy);
_legacy = nullptr;
}
}
现在,我想确保只有一个遗留资源由 Thing
的一个实例管理。我不希望 Thing
class 的消费者随意传递实例——它们必须在本地属于 class 或函数,直接或通过 unique_ptr
,或者用可以传递的 shared_ptr
包裹起来。为此,我删除了赋值运算符和拷贝构造函数...
private:
Thing(Thing const&) = delete;
void operator=(Thing const&) = delete;
然而,这增加了一个额外的挑战。要么我必须将我的工厂方法更改为 return a unique_ptr<Thing>
或 shared_ptr<Thing>
,要么我必须实现移动语义。我不想规定应该使用 Thing
的模式,所以我选择添加一个移动构造函数和移动赋值运算符,如下所示...
Thing(Thing&& old) noexcept : _legacy(old._legacy)
{
// Reset the old thing's state to reflect the move
old._legacy = nullptr;
}
Thing& operator= (Thing&& old) noexcept
{
if (&old != this)
{
swap(_legacy, old._legacy);
}
return (*this);
}
完成这一切后,我可以使用 Thing
作为本地并移动它...
Thing one = Thing::Acquire();
Thing two = move(one);
我无法通过尝试提交自我分配来打破模式:
Thing one = Thing::Acquire();
one = one; // Build error!
我也可以做一个unique_ptr
给一个...
auto three = make_unique<Thing>(Thing::Acquire());
或者 shared_ptr
...
auto three = make_shared<Thing>(Thing::Acquire());
一切都如我所料,我的析构函数 运行 在我所有的测试中都恰到好处。事实上,唯一让人恼火的是 make_unique
和 make_shared
都 实际上 调用了移动构造函数——它没有像我希望的那样被优化掉。
第一个问题:我是否正确实现了移动-构造函数和移动赋值运算符? (它们对我来说相当陌生,这将是我第一次在愤怒中使用它们。)
第二个问题:请对这个图案进行评论!这是将遗留资源包装在 C++14 class 中的好方法吗?
最后: 我是否应该更改任何内容以使代码更好、更快、更简单或更具可读性?
您应该将 Thing
包装在智能指针中,这样您就无需担心复制和移动语义。
class Thing
{
private:
void* _legacy;
public:
void Operation1(...);
int Operation2(...);
string Operation3(...);
Thing(const Thing&) = delete;
Thing(Thing&&) = delete;
Thing& operator=(const Thing&) = delete;
Thing& operator=(Thing&&) = delete;
static std::shared_ptr<Thing> acquire() {
return std::make_shared<Thing>();
}
private:
Thing() : _legacy(malloc(16)) {
// ...
}
~Thing() {
free(_legacy);
}
};
同样,你可以用unique_ptr
来做:
std::unique_ptr<Thing> acquire() {
return std::make_unique<Thing>();
}
你似乎暗示你只想拥有这个东西的一个实例,尽管即使在你的解决方案中你也没有尝试做那样的事情。为此,您需要静态变量。不过请记住,在这种情况下,您的资源只会在 main()
函数退出后才会被释放。例如:
static std::shared_ptr<Thing> acquire() {
static std::shared_ptr<Thing> instance;
if (!instance) {
instance = std::make_shared<Thing>();
}
return instance;
}
或unique_ptr
版本:
static Thing& acquire() {
static std::unique_ptr<Thing> instance;
if (!instance) {
instance = std::make_unique<Thing>();
}
return *instance;
}
或者,您可以使用weak_ptr
在程序范围内获取一个实例,当没有人使用它时释放它。在这种情况下,您将无法使用 unique_ptr
来达到此目的。如果对象被释放然后再次需要,此版本将重新创建对象。
static std::shared_ptr<Thing> acquire() {
static std::weak_ptr<Thing> instance;
if (instance.expired()) {
instance = std::make_shared<Thing>();
}
return instance.lock();
}
struct free_thing{
void operator()(void* p)const{
// stuff
free(p);
}
};
using upthing=std::unique_ptr<void,free_thing>;
upthing make_thing(stuff){
void*retval;
// code
return upthing(retval);
}
将 upthing
作为 _legacy
存储在您的 Thing
中。使用默认 dtor,移动 ctor,为 Thing
(=default
) 移动赋值。
销毁代码进入free_thing
。
你的ctor创造了东西。
现在用户可以将您的 Thing
视为仅移动值类型。
除非确实需要,否则不要编写自己的指针管理器。独特和共享为您做很多事情:如果您编写自己的智能指针,甚至可以将它们用作内部胆量。