【问题标题】:How to a mark a function as invalidating its argument如何将函数标记为使其参数无效
【发布时间】:2015-03-15 18:26:06
【问题描述】:

我有一个函数f,它接受一个指针向量。一旦函数f 完成,这些指针就不再有效。请注意,没有真正需要更改向量本身,我只是想鼓励调用者在调用f 之后不要使用指针。 f 有三种可能的签名:

移动签名

void f(vector<void*> &&v); // because the pointers in v are no longer valid. 
// This signature also allows me to have f call clear() on v.

const 签名

void f(const vector<void*> &v); // because the pointers in v are no longer valid,
// but we don't have to change the vector v.

指针签名

void f(vector<void*> *v); // The functino modifies v in a predictable way 
// (it clears it). A pointer is used instead of a reference so that
// calls to the function will have a '&' which clearly shows to the reader
// that this function modifies its argument. '*' is different from '&&' since '&&' 
// may imply "do not use v, but it is unknown how it will be modified" while 
// '*' implies a clear semantic for how v is changed.

在 C++11 中使用哪个签名更惯用?

【问题讨论】:

  • const vector&amp; 根本不允许您修改向量,并且只有在“无参数”是一个合理的参数时传递指针才有意义。第一个变体不接受左值,这很奇怪。只需按值传递,让调用者决定他想要移动还是复制。
  • 这些签名实际上都没有说明/保证关于向量内的指针(更不用说指针)的任何内容,而只是关于向量本身。
  • @KonradRudolph - &amp;&amp; 签名在语义上意味着“这个函数可以将v 修改为可破坏的所有内容,所以不要依赖 v 的内容”,这非常接近,不要你觉得呢?
  • @tohava 不。就像你说的,签名说“不要依赖向量的内容”,但这无关紧要。如果调用者有不同的指针指向同一个资源怎么办?如果指针表示所有权 (unique_ptr),您的签名将有意义。
  • @tohava 并不是说​​这不完美,而是会产生误导:没有人会按照您的意图理解签名,因为类型系统根本不传达此信息;您的签名传达了不同的正交信息。你有两个选择:要么正确地编码它(例如通过使用前面提到的unique_ptrs),要么根本不尝试(错误地)在类型系统中编码它,而是依赖文档和测试。跨度>

标签: c++ c++11 types move-semantics rvalue-reference


【解决方案1】:

怎么样

void f(vector<void*> v);

并使用它:

vector<void*> myVec = /*...*/;
f(std::move(myVec));

如果f 逻辑上需要向量的所有权,这是惯用的方式。它允许调用者决定是将向量移动还是复制到f

如果调用者实际上希望f 修改他的向量(因此向量实际上是一个输入/输出参数),那么这不适合您的需求。然而,输入/输出参数很糟糕。函数应该将输入作为参数,并将输出作为返回值。这就是上帝的本意。

【讨论】:

  • 为什么这比&amp;&amp; 版本好,又比我的其他建议好在哪里?我试图了解这三个中哪一个是最好的以及为什么
  • 向量是一个包含指针/句柄/任何东西的向量。没有真正需要修改它,但我想向调用者澄清他不应该再使用这些指针/句柄。
  • 如果f 删除了指针,那么这是最糟糕的选择(如果调用者忘记写入std::move,它将给调用者留下一个不确定的指针向量)
  • 你应该添加一个删除的左值重载,可以防止意外不动。
【解决方案2】:

如果你真的想用类型系统来做这件事,总是可以使用你自己的类型来编码额外的信息。

template<class T>
struct invalidates_contained_pointers;

template<class T>
invalidates_contained_pointers<T>* contents_will_be_invalidated(T* ptr) {
    return reinterpret_cast<invalidates_contained_pointers<T>*>(ptr);
}

void f(invalidates_contained_pointers<vector<void*>> *v){
    auto pv = reinterpret_cast<vector<void*> *>(v);
    // ...
}

f(contents_will_be_invalidated(&vec));

类似的方法可用于参考。

【讨论】:

    【解决方案3】:

    简短的回答:没有办法做到这一点。唯一的“官方”是相反的:一个签名,承诺函数f(..) 不会改变它的参数:const 关键字。

    通常遵守以下规定:

    • 不修改其参数的函数要么将其参数作为按值复制,要么用const显式标记其参数

    • 通过非常量引用、移动或指向非常量对象的指针传递的参数应被理解为“该参数很可能被调用的函数f(...) 修改”。

    【讨论】:

      【解决方案4】:

      正如其他人所说,类型系统不允许您指示“在此函数调用后不要使用此数据”之类的内容。你可以做什么:

      void f(vector<void*> &v)
      {
        // ... use v ...
        v.clear(); // encourage callers not to use the pointers after the call
      }
      

      【讨论】:

        【解决方案5】:

        f 如果要删除指针(或释放它们的句柄),则应清除向量。给调用者留下一个不确定值的向量是毫无意义的危险。

        所以f 应该通过非常量引用接受向量。是否要进行此左值引用或右值引用取决于您;但左值版本似乎更简单。

        【讨论】:

          【解决方案6】:

          在这三个中:vector&lt;void*&gt; * 需要一个左值来获取地址。 const vector&lt;void*&gt; &amp; 允许传入左值或右值。vector&lt;void*&gt; &amp;&amp; 只允许传入右值。

          根据您的问题,使用左值或右值调用您的函数是有意义的,因此const vector&lt;void*&gt; &amp; 是显而易见的选择。

          没有办法通过类型系统指示调用者应该停止使用包含的指针,并且您不应该尝试通过类型系统来指示。通过文档表明这一点。

          【讨论】:

            猜你喜欢
            • 2015-08-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-05-25
            • 1970-01-01
            • 2020-03-10
            • 1970-01-01
            相关资源
            最近更新 更多