【问题标题】:Storing const* in const vector将 const* 存储在 const 向量中
【发布时间】:2020-02-18 16:48:23
【问题描述】:

我必须处理的相关接口代码包含两个函数,一个是检索对象,另一个是我必须将对象作为向量提交。问题是,检索函数返回const Object*,但提交函数需要const vector<Object*>

我知道这可以通过 const_cast<Object*> 解决,但有没有其他更简洁的方法?

下面是演示问题的代码:

#include <vector>

//////////// REPRESENTATIVE INTERFACE IMPLEMENTATION, DO NOT TOUCH ///////
struct Object{};

class Interface{
    public:
    size_t getNumObjects() const {return 10;}
    const Object* getObject(size_t index) const {return nullptr;}
};
const Interface interface;

void submitObjects(const std::vector<Object*> &objects);
//////////////////////////////////////////////////////////////////////

// Task: take all objects from 'interface' and submit them to 'submitObjects'.

int main(){

    std::vector<const Object*> objects;
    for(size_t i = 0; i < interface.getNumObjects(); i++){
        const Object* object = interface.getObject(i);
        objects.push_back(object);
    }

    submitObjects(objects); // ERROR: no known conversion from 'vector<const Object *>' to 'const vector<Object *>'

    return 0;
}

我能想出解决这个问题的唯一方法是使objects 成为std::vector&lt;Object*&gt; 并插入带有objects.push_back(const_cast&lt;Object*&gt;(object)); 的对象,但我觉得必须有更好的解决方案。

感谢任何想法。


更多背景信息为什么这是一个有效的问题: const vector&lt;Object*&gt; 只能传递 const Object*,正如其所有 getter 重载所示,如下所示:https://de.cppreference.com/w/cpp/container/vector/operator_at

因此,vector&lt;const Object*&gt; 几乎从不在接口中使用。因此,将大量const Object* 组合成一个大const vector&lt;Object*&gt; 的想法是一个有效的概念,但如果没有const_cast,似乎无法构建。

编辑:以上陈述不正确,感谢澄清。 const std::vector&lt;Object*&gt; 可以产生非常量指针,因为我对指针常量的理解不正确。

【问题讨论】:

  • std::vector&lt;Object*&gt; conststd::vector&lt;Object const*&gt; 是两个完全不同的野兽!在后者中,您可以更改向量的内容(添加/删除/替换),但不能修改指向的对象。在前者中,向量本身不能被修改——但是only适用于指针,而你可以修改指向的对象!
  • @Finomnis 我很确定const vector&lt;Object*&gt; 给了你Object* const(即一个指向可变对象的const 指针,它可以对应一个const Object*,即可变指针指向const 对象)。
  • @Finomnis 您误解了文档/打字规则。如果你有T = Object*,那么const T 不是const Object* 而是Object* const
  • West-const-ing 的危险!多年来,我一直是一名东部居民。
  • @Everyone ...所以我是 const 指针符号的受害者吗?

标签: c++ c++11 vector c++14 constants


【解决方案1】:

您误解了文档/打字规则。如果你有T = Object*,那么const T 不是const Object* 而是Object* const。这就是 C 和 C++ 的工作原理。

因此,您断言“您无法从 const vector&lt;Object*&gt; 获取可变对象是错误的。您不能做的是修改向量或元素的值(即更改指针)。但是访问器返回对 @ 的引用987654328@,所以仍然可以修改指向的对象:

void foo(const std::vector<int*>& v)
{
    (*v[0])++; // Increments the integer pointed to by the first vector element.
}

https://godbolt.org/z/HGIb20

这仅仅是因为const 不具有传递性。这是另一个例子:

struct X
{
    int* y;
};

void foo(const X& x)
{
    (*x.y)++;
}

https://godbolt.org/z/NmCwGt


鉴于此,您拥有的界面不允许您做任何事情。您只获得了指向不可变对象的指针,但应该传递指向可变对象的指针(submitObjects 函数仅承诺不修改容器,但保留修改对象的权利)。这根本不可能。

const_cast 可能是一个有效的选项 if(这是一个非常重要的 if!)你知道 100% 的对象没有实际上声明为 const无论它们是在哪里创建的。但这既破坏了界面的意义,也是一种极其脆弱、不合时宜的方法,在这种情况下您甚至不应该考虑。

【讨论】:

    【解决方案2】:

    您的接口返回指向常量对象的指针。您想将这些常量对象发送到需要可变对象的函数中。

    这不仅仅是关于未定义的行为,而是关于惊喜。

    想象一下,实现该接口的方法是保留有关对象的计算值,并在调用可能发生变异的方法时标记为脏。确实有一个令人信服的理由来返回指向常量对象的指针。修改 const 对象会破坏类不变量。

    如果您将该常量发送到submitObjects 并且您的对象在那里发生了变异,那么您在某种意义上是在撒谎:接口向您发送常量对象并且您对其进行了变异。你违反了合同。

    如果submitObjects 不改变它的参数,那么就没有理由保持参数可变。


    注意关于 const 的混淆,这里有一个指向可变数据的指针的 const 向量示例:

    // const std::vector<int*>
    auto const cannot_erase_or_add = std::vector<int*>{
        new int{1},
        new int{2},
        new int{3}
    };
    
    // Error!
    // cannot_erase_or_add.push_back(new int{4});
    
    // Error!
    // cannot_erase_or_add.clear();
    
    // operator[] return int*, because it's a vector of int*
    int* one = cannot_erase_or_add[0];
    
    // totally legal, int*, no const there
    *one = 2;
    

    另一方面,情况恰恰相反:

    // std::vector<int const*>
    auto can_erase_or_add = std::vector<int const*>{
        new int{1},
        new int{2},
        new int{3}
    };
    
    // Works
    can_erase_or_add.clear();
    
    // Works
    can_erase_or_add.push_back(new int{4});
    
    // operator[] return int const*, because it's a vector of int const*
    int const* four = can_erase_or_add[0];
    
    // Can't do that, pointer to const
    // *four = 2;
    

    【讨论】:

    • 或者,说白了:提问者想做什么是不可能的。
    • 正如我刚刚在回答中添加的那样,从const vector&lt;Object*&gt; 获取可变对象是不可能的,因此接口通常不需要vector&lt;const*&gt;
    • @Finomnis 没有。指向可变数据的指针的常量向量返回指向可变数据的指针。 std::vector 不会通过指针和模板在那里添加 const。
    【解决方案3】:

    您的选择是:

    1. submitObjects 更改为接受std::vector&lt;const Object*&gt;
    2. 或更改Interface::getObject 以返回指向非常量Object 的指针。
    3. 或使用const_cast,如您所见。尽量避免这种情况。

    如果submitObjects 修改了指向的对象,那么如果不应该修改指向的对象(例如它们实际上是 const 对象),则 3. 是危险的。如果可以修改对象,则选择2。

    如果submitObjects没有修改指向的对象,那么修改1应该没有问题。

    如果submitObjects修改了指向的对象,而要修改的对象是不行的,那么你就有逻辑上的矛盾了,除了不将指针传递给函数之外没有其他解决办法。

    不可能从const vector&lt;Object*&gt; 中检索可变指针?

    只有指针不能变异。但是你可以改变指向的对象。示例:

    struct Object{ int member; };
    Object o;
    const vector<Object*> objects{&o};
    objects[0]->member = 42; // well-defined
    

    我在考虑 1.,但这意味着它将不再接受 std::vector,需要另一个重载

    或者,您可以使用模板。特别是,这可能是输入迭代器或范围的一个很好的用例。

    【讨论】:

    • 我同意你的解释。我有几个后续问题:submitObjects 是否可以修改指向的对象,因为不可能从const vector&lt;Object*&gt; 检索可变指针?是的,我在考虑 1.,但这意味着它将不再接受 std::vector,需要另一个重载...
    • @Finomnis "但这意味着它将不再接受 std::vector,需要另一个重载...":不完全是,因为您可以安全地将 const_cast 从非 const 转换为 const。
    猜你喜欢
    • 2019-05-27
    • 1970-01-01
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 2012-11-07
    • 1970-01-01
    • 2023-01-31
    • 2011-09-07
    相关资源
    最近更新 更多