C++ Move Semantics - 包装旧版C API

Xha*_*lie 11 c++ constructor move c++11 c++14

我正在使用传统的C API,在这种API下获取一些资源是昂贵的,并且释放该资源绝对是至关重要的.我正在使用C++ 14,我想创建一个类来管理这些资源.这是事物的基本骨架......

class Thing
{
private:
    void* _legacy;

public:
    void Operation1(...);
    int Operation2(...);
    string Operation3(...);

private:
    Thing(void* legacy) :
        _legacy(legacy)
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

这不是单身模式.没有什么是静态的,可能有很多Thing实例,都在管理自己的遗留资源.此外,这不仅仅是一个智能指针.包裹的指针,_legacy是私有的,所有操作都是通过一些公共实例函数公开的,这些函数隐藏了消费者的遗留API.

构造函数是私有的,因为实例将从实际获取资源Thing的静态工厂或命名构造函数返回.这是对该工厂的廉价模仿,使用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);
    }
Run Code Online (Sandbox Code Playgroud)

这是析构函数,它负责释放遗留资源,同样,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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在,我想确保只有一个实例管理一个遗留资源Thing.我不希望类的消费者随意Thing传递实例 - 它们必须直接或通过unique_ptr或者用一个包装来本地拥有类或函数.shared_ptr也可以包含可以传递的.为此,我删除了赋值运算符和复制构造函数......

private:
    Thing(Thing const&) = delete;
    void operator=(Thing const&) = delete;
Run Code Online (Sandbox Code Playgroud)

然而,这增加了额外的挑战.要么我必须改变我的工厂方法来返回一个unique_ptr<Thing>或一个shared_ptr<Thing>或我必须实现移动语义.我不想指定Thing应该使用的模式,所以我选择添加一个move-constructor和move-assignment-operator如下...

    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);
    }
Run Code Online (Sandbox Code Playgroud)

完成所有这些后,我可以将其Thing用作本地并将其移动...

    Thing one = Thing::Acquire();
    Thing two = move(one);
Run Code Online (Sandbox Code Playgroud)

我无法通过尝试提交自我分配来破坏模式:

    Thing one = Thing::Acquire();
    one = one;                      // Build error!
Run Code Online (Sandbox Code Playgroud)

我也可以做unique_ptr一个......

    auto three = make_unique<Thing>(Thing::Acquire());
Run Code Online (Sandbox Code Playgroud)

或者shared_ptr......

    auto three = make_shared<Thing>(Thing::Acquire());
Run Code Online (Sandbox Code Playgroud)

一切都按照我的预期运作,我的析构函数在我的所有测试中都在正确的时刻运行.事实上,唯一的烦恼是,make_unique并且make_shared两者实际上都调用了移动构造函数 - 它没有像我希望的那样进行优化.

第一个问题:我是否正确实现了move -constructor和move-assignment-operator?(他们对我来说相当新,这将是我第一次愤怒地使用它.)

第二个问题:请评论这种模式!这是将遗留资源包装在C++ 14类中的好方法吗?

最后:我是否应该更改任何内容以使代码更好,更快,更简单或更易读?

pet*_*ohn 6

你应该用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);
    }
};
Run Code Online (Sandbox Code Playgroud)

同样,你可以用unique_ptr:

std::unique_ptr<Thing> acquire() {
    return std::make_unique<Thing>();
}
Run Code Online (Sandbox Code Playgroud)

你似乎暗示你只想拥有这个东西的一个实例,尽管在你的解决方案中你并没有尝试做任何类似的事情.为此你需要静态变量.虽然请记住,在这种情况下,只有在main()函数退出后才会释放资源.例如:

static std::shared_ptr<Thing> acquire() {
    static std::shared_ptr<Thing> instance;
    if (!instance) {
        instance = std::make_shared<Thing>();
    }
    return instance;
}
Run Code Online (Sandbox Code Playgroud)

unique_ptr版本:

static Thing& acquire() {
    static std::unique_ptr<Thing> instance;
    if (!instance) {
        instance = std::make_unique<Thing>();
    }
    return *instance;
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用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();
}
Run Code Online (Sandbox Code Playgroud)

  • @Xharlie但是你可以为智能指针提供自定义删除器的原则仍然有效. (3认同)

Yak*_*ont 2

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);
}
Run Code Online (Sandbox Code Playgroud)

将 a 存储upthing在您的Thingas中_legacy。使用默认 dtor、移动 ctor、移动分配Thing( =default)。

销毁代码进入free_thing

你的演员创造了一切。

现在用户可以将您视为Thing仅移动值类型。

除非确实需要,否则不要编写自己的指针管理器。独特和共享可以为您做很多事情:如果您编写自己的智能指针,甚至可以将它们用作内部结构。