4

我正在调整在 cppcon 2016 中谈到的延迟 ptr Herb Sutter 的想法,以便能够以更安全的方式管理由 id 表示的外部资源。

因此,我创建了一个不可复制且只能移动的类,该类持有id该资源应表示的内容。如果对象被移动到另一个对象,就像应该成为unique_ptr的那样。id0

据我了解,如果被调用的函数没有任何先决条件,即使在被移动之后,您仍然应该被允许使用该对象,所以据我了解,这应该是有效的:

int main() {

    resource src = make_resource(10);
    resource dst;
    std::cout << "src " << src.get() << std::endl;
    std::cout << "dst " << dst.get() << std::endl;

    dst = std::move(src);
    std::cout << "src " << src.get() << std::endl; // (*)
    std::cout << "dst " << dst.get() << std::endl;

    src = make_resource(40);
    std::cout << "src " << src.get() << std::endl;
    std::cout << "dst " << dst.get() << std::endl;

    return 0;
}

但是clang-tidy给我这个警告:

警告:“src”在移动后使用 [bugprone-use-after-move]

对于src.get()之后dst = std::move(src)(上面标记)。

所以我的问题是:

  1. src.get()之后我可以打电话吗std::move(src)
  2. 我可以src.get()假设0std::move.
  3. 如果1.2. 有效,那么有没有办法更改代码,以便 clan-tidy 知道这是有效的。如果没有,是否有办法更改有效的代码?

这是该类的实现:

struct resource {
    resource() = default;

    // no two objects are allowed to have the same id (prevent double free, only 0 is allowed multiple times as it represents nullptr)
    resource(const resource&) = delete;
    resource& operator=(const resource& other) = delete;

    // set the id of the object we move from back to 0 (prevent double free)
    resource(resource&& other) noexcept : id(std::exchange(other.id, 0)) {}
    resource& operator=(resource&& other) noexcept {
        id = std::exchange(other.id, 0);
        return *this;
    }

    // will free the external resource if id not 0
    ~resource() = default;

    // returns the id representing the external resource
    int get() const noexcept { return id; }

  protected:
    // only allow the make function to call the constructor with an id
    friend resource make_resource(int id);
    explicit resource(int id) : id(id) {}

  protected:
    int id = 0; // 0 = no resource referenced
};

// in the final version the id should be retrieved by from the external ip
resource make_resource(int id) { return std::move(resource(id)); }
4

2 回答 2

8
  1. 我可以在 std::move(src) 之后调用 src.get()

如果我们对 的类型保持不可知论src,那么我们不知道。可能不会。在某些情况下,在 move 之后调用成员函数是未定义的。例如,调用智能指针的间接运算符。

鉴于decltype(src)您展示的定义及其成员函数,我们知道:是的,您可以这样做。

  1. 我可以假设 src.get() 在 std::move 之后返回 0。

如果我们对 的类型保持不可知论src,我们对做什么一无所知src.get()。更具体地说,我们不知道它的先决条件。

鉴于decltype(src)您展示的定义及其成员函数:是的,我们可以做出假设。

  1. 如果 1. 和 2. 有效,那么有没有办法更改代码,以便 clan-tidy 知道这是有效的。如果没有,是否有办法更改有效的代码?

Clang-tidy 假设“不处于从状态移动”是所有成员函数(赋值除外)的前提条件,并且在该假设下,警告这种假设的前提条件正在被违反。因此,即使您碰巧知道您的班级不存在这种预编码,它也试图强制执行一个约定以始终假定这种预编码。

您可以删除移动之间的调用src.get(),并重新分配src. clang-tidy 没有抱怨的对已移动变量的一项操作是重新分配,在分配之后,对象的状态应该(通常)被很好地定义并且调用其他成员函数被认为是好的(当然,您可以有其他必须满足的先决条件,但 clang-tidy 可能不知道它们)。尽管从技术上讲,可以定义一个类型,即使移动后的赋值也没有很好地定义,但这样的类型将是非常非常规和不安全的。


总而言之,您可以在移动之后(甚至在重新分配之前)为这个特定的类调用src.get(),但是您将不会遵循 clang-tidy 试图强制执行的约定。

于 2019-01-11T14:03:43.923 回答
6

cppreference.com 有这样的文字

除非另有说明,否则所有已移出的标准库对象都处于有效但未指定的状态。也就是说,只有没有前置条件的函数,例如赋值运算符,才能在对象被移动后安全地用于对象:

因此,非正式地,C++约定是移出对象将是有效但无用的,这就是为什么 clang-tidy 建议使用它是可疑的。

对于您的实现,您提供的不仅仅是约定——因此您的代码没有错,只是非常规。

于 2019-01-11T14:02:00.687 回答