C++ 是",;移动后使用[移动后易出错使用]“;警告这里真的有问题吗?

C++ 是",;移动后使用[移动后易出错使用]“;警告这里真的有问题吗?,c++,c++17,move,clang-tidy,C++,C++17,Move,Clang Tidy,我正在采用cppcon 2016中提到的不同ptr Herb Sutter的理念,以便能够以更安全的方式管理id所代表的外部资源 因此,我创建了一个不可复制且仅可移动的类,它持有资源应该表示的id。与unique\u ptr类似,如果将对象移动到另一个对象,则id应变为0 据我所知,即使在对象被移动之后,如果被调用函数没有任何先决条件,您仍然应该被允许使用该对象,因此据我所知,这应该是有效的: int main() { resource src = make_resource(10);

我正在采用cppcon 2016中提到的不同ptr Herb Sutter的理念,以便能够以更安全的方式管理id所代表的外部资源

因此,我创建了一个不可复制且仅可移动的类,它持有资源应该表示的
id
。与
unique\u 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;
}
cppreference.com有:

除非另有规定,否则所有具有 已从中移动的对象处于有效但未指定的状态。就是, 只有不带前置条件的函数,例如赋值 操作员,可在对象从以下位置移动后安全使用:

P> >,非正式地,C++约定是从对象移动的是无效的,这就是为什么CLAN-TIY建议使用它是可疑的。 对于您的实现,您提供了“更多”而非约定——因此您的代码没有错,只是非常规

  • 我可以在std::move(src)之后调用src.get()吗
  • 如果我们对
    src
    的类型仍然不可知,那么我们就不知道了。可能不会。在某些情况下,在移动后调用成员函数是未定义的。例如,调用智能指针的间接运算符

    根据您展示的
    decltype(src)
    及其成员函数的定义,我们知道:是的,您可以

  • 我可以假设src.get()在std::move之后返回0
  • 如果我们对
    src
    的类型仍然不可知,那么我们对
    src.get()
    的功能一无所知。更具体地说,我们不知道它的先决条件

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

  • 如果1。二,。如果代码是有效的,那么是否有办法更改代码,使clan tidy知道这是有效的。如果没有,是否有办法更改代码,使其有效
  • Clang tidy假设“不处于从状态迁移的状态”是所有成员功能(分配除外)的先决条件,并且在该假设下,警告正在违反该假设的先决条件。因此,它试图强制约定始终假定这样的预条件,即使您碰巧知道您的类不存在这样的预条件

    您可以在移动和重新分配
    src
    之间删除对
    src.get()
    的调用。clang tidy没有抱怨的moved from变量上的一个操作是重新赋值,在赋值之后,对象的状态应该(按照惯例)得到很好的定义,并且调用其他成员函数被认为是正常的(当然,你可以有其他必须满足的先决条件,但clang tidy可能不知道这些条件)。虽然从技术上讲,可以定义一种类型,即使是移动后的分配也没有很好的定义,但这种类型将非常非传统和不安全



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

    我认为这很好,但是clang tidy不知道你的智能指针及其语义,因此假设情况更糟。你没有编写代码吗?如果是,你应该是回答这些问题的最佳人选。没有太大区别在从
    资源
    移动的一个对象和一个恰好具有
    id==0
    的有效对象之间,没有?@juanchopanza如果我使用调试器,那么我看到一切都按预期工作。但这并不意味着这是未定义的行为。@user463035818据我所知,这个类管理的任何有效对象都不能具有id=0,由外部API保证。这是有道理的。我非常关注我的实现是否正确,以至于我忘记了一个事实,即在移动后,即使对象是正确的,您自然也不会访问它。因此在正常使用情况下,不会出现叮当声警告,因为您永远不应该调用
    .get()
    直接在移动之后,而这正是clang tidy警告我的。但是我可以稍后在其他地方调用
    .get()
    来检查我的类是否仍在引用外部资源,或者“所有权”是否被移走。@t.niese就是这样,有时你会调用
    .get()
    ,因为这完全取决于要从中移动的对象的类型。这就是我为什么对代码的作者发表评论的原因。clang tidy必须做出假设才能正常工作,有时这些假设是错误的。公平地说,这句话没有提到非标准库对象-我意识到你是故意的这是一个非正式的惯例,因为你意识到你在扩大报价的范围,这不是不合理的。但我不确定我们是否能走得那么远,我想这就是我想说的。嗯“有效但无用”对于其接口没有可调用的前置条件的对象来说有点强。实现只提供了约定的内容。只是
    pre()
    没有前置条件。我的意思是
    get()
    ,而不是
    pre()“LeNNESS RESIMSNORE”非正式约定“可能不是正确的术语,但我的意思是,IMO,设计一些东西是不明智的,即使C++不能执行,也会让那些理解C++标准库的人感到惊讶。举个愚蠢的例子,除了惯例,没有什么能阻止你做容器。班级里的冰毒在哪里
    
    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)); }