我正在适应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)
(如上所述)。
所以我的问题是:
src.get()
之后打电话给std::move(src)
src.get()
在0
之后返回std::move
。这是该类的实现:
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)); }
答案 0 :(得分:5)
cppreference.com具有this text:
除非另有说明,否则所有具有 被移出的状态将被置于有效但未指定的状态。那是, 仅具有前提条件的功能,例如分配 运算符,可以从以下位置安全地用于对象:
因此,非正式地,C ++ 惯例表示移出的对象将是有效但无用的,这就是clang-tidy建议使用它的可疑之处。
对于您的实现,您提供的不仅仅是约定俗成的内容-因此您的代码没有错,只是非常规的。
答案 1 :(得分:5)
- 我允许在std :: move(src)之后调用src.get()
如果我们仍然不知道src
的类型,那么我们就不知道了。可能不会。在某些情况下,移动后调用成员函数将不确定。例如,调用智能指针的间接操作符。
考虑到decltype(src)
的定义及其成员函数,我们知道:是的,允许这样做。
- 我可以假设src.get()在std :: move之后返回0。
如果我们对src
的类型仍然不确定,那么我们对src.get()
的工作一无所知。更具体地说,我们不知道其先决条件。
给出decltype(src)
的定义及其成员函数:是的,我们可以做个假设。
- 如果1.和2.有效,那么有一种方法可以更改代码,以使clan-tidy知道这是有效的。如果没有,是否可以更改有效的代码?
Clang-tidy假定“不在状态中”是所有成员函数(除了赋值)的先决条件,并且在该假设下警告说,这样的前提条件已被违反。因此,即使您碰巧知道班级中不存在这样的预编码,它也会尝试强制执行惯例以始终采用这种预编码。
您可以在移动和重新分配src.get()
之间删除对src
的呼叫。从clang-tidy不会抱怨的变量移出的一个操作是重新分配,并且在分配之后,对象的状态(通常)应该得到很好的定义,并且调用其他成员函数被认为是可以的(当然) ,您可以满足其他先决条件,但clang-tidy可能不知道这些前提条件。 尽管从技术上讲,可以定义一种类型,即使移动后的赋值也不能很好地定义,但是这种类型会非常不常规且不安全。
最后,您可以 为此特定班级移动后(甚至在重新分配之前)呼叫src.get()
,但是您不会遵循clang-tidy试图尝试的惯例强制执行。