这里的“移动后[bugprone-use-after-move]使用后”是否在警告一个真正的问题?

时间:2019-01-11 13:48:49

标签: c++ c++17 move clang-tidy

我正在适应cppcon 2016中讨论的递延ptr Herb Sutter的想法,以便能够以更安全的方式管理由id表示的外部资源。

因此,我创建了一个不可复制的且只有可移动的类,其中包含资源应代表的id。像unique_ptr一样,如果将对象移动到另一个对象,则id应该变成0

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

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()0之后返回std::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)); }

2 个答案:

答案 0 :(得分:5)

cppreference.com具有this text

  

除非另有说明,否则所有具有   被移出的状态将被置于有效但未指定的状态。那是,   仅具有前提条件的功能,例如分配   运算符,可以从以下位置安全地用于对象:

因此,非正式地,C ++ 惯例表示移出的对象将是有效但无用的,这就是clang-tidy建议使用它的可疑之处。

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

答案 1 :(得分:5)

  
      
  1. 我允许在std :: move(src)之后调用src.get()
  2.   

如果我们仍然不知道src的类型,那么我们就不知道了。可能不会。在某些情况下,移动后调用成员函数将不确定。例如,调用智能指针的间接操作符。

考虑到decltype(src)的定义及其成员函数,我们知道:是的,允许这样做。

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

如果我们对src的类型仍然不确定,那么我们对src.get()的工作一无所知。更具体地说,我们不知道其先决条件。

给出decltype(src)的定义及其成员函数:是的,我们可以做个假设。

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

Clang-tidy假定“不在状态中”是所有成员函数(除了赋值)的先决条件,并且在该假设下警告说,这样的前提条件已被违反。因此,即使您碰巧知道班级中不存在这样的预编码,它也会尝试强制执行惯例以始终采用这种预编码。

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


最后,您可以 为此特定班级移动后(甚至在重新分配之前)呼叫src.get(),但是您不会遵循clang-tidy试图尝试的惯例强制执行。