Const vector of non-const objects

28
在定义接口中的一个函数:
virtual void ModifyPreComputedCoeffs ( std::vector < IndexCoeffPair_t > & model_ ) = 0;
我们希望指定 vector model_ 不应被更改,也就是说不能在 vector 上执行 push_back 等操作,但是可以更改 model_ 中的 IndexCoeffPair_t 结构对象。如何指定?
virtual void ModifyPreComputedCoeffs ( const std::vector < IndexCoeffPair_t > & model_ ) = 0;

我想不起来它是怎么工作的。


1
不起作用,你认为呢?看起来应该可以工作,你试过修改它了吗? - yan
1
@yan:不,它不应该工作,因为vector<T>::operator[] const和所有其他const元素访问器都返回vector<T>::const_reference - Josh
1
@yan,一个常量向量只会返回指向内容的常量引用 - 你无法修改它们。 - Mark Ransom
1
@yan:我的意思是我尝试过了,但不起作用。不确定是否做错了什么。现在明白了原因。谢谢Mark和user349433。 - Humble Debugger
6个回答

19

不要将向量传递到函数中,而是像标准库一样传递一个迭代器对。

virtual void ModifyPreComputedCoeffs ( std::vector < IndexCoeffPair_t >::iterator & model_begin, std::vector < IndexCoeffPair_t >::iterator & model_end )

3
你在回避问题。如果他/她需要传递一个这样的向量的向量,该怎么办? - 6502
@6502,你觉得这似乎有点超出原问题的范围了吧? - Mark Ransom
不完全是。他问如何指定这样的向量,而你建议他不应该传递一个向量,而是两个迭代器。虽然在这种特定情况下可能有效,但会有一些小麻烦(需要两个参数而不是一个),但它并不具有可扩展性,并且没有回答原始问题(如何指定该向量)。不幸的是,在C++中的答案是“你不能,你必须定义自己的向量类来实现”。 - 6502
@6502:我不明白你说的它不能扩展的意思。你可以编写相对简单的展平迭代器,轻松处理深度。我也不太喜欢const,但我认为你在这里没有强有力的观点。 - Puppy
3
大多数情况下,人们不想听到“那是不可能的”,他们想知道如何实现它。 - Mark Ransom
@DeadMG:假设我想将一个从字符串到非常量对象的常量向量的映射传递给一个函数,你如何简单地展开它? - 6502

7
C++中的const-correctness概念在我看来被高估了。你发现的其中一个主要限制是它不适用于组合式。如果想要创建一个非const对象的const向量,则需要实现自己的向量类型。请注意,例如标准库甚至也必须为const_iterators引入新类型。
我的建议是在强制使用时使用const-correctness,而不是任何地方都使用。理论上,const-correctness应该有助于程序员,但由于语法非常原始(只有一位,并且不适用于组合式,甚至需要代码复制),所以代价非常高。
此外,在我的经验中,这种所谓的大帮助并不是真正的大帮助...它捕获的大多数错误与const-correctness机制本身有关,而不是程序逻辑。
你是否曾想过为什么大多数语言(包括C++之后设计的语言)没有实现这个想法?

12
无论个人意见如何,C++ 中都存在 const 正确性,并且这是一个重要的特性,如果避免不当,可能会在生产代码中造成严重的头痛和时间、金钱的损失。虽然我赞同这种观点(并且并不完全不同意),但它更像是对原问题的评论而不是答案。6502 - 你曾经对 Mark Ransom 的 const_iterator 答案做出了类似的争论。 - Kit10
1
@Copperpot:是的,C++中存在const正确性,无论你喜欢与否,你都必须处理它。这是一种所谓的“按需付费”哲学不适用的情况之一,因为即使你不想要它,你也必须为它付出代价。在我使用C++的经验中,我甚至不能记得有一个(仅仅一个)const正确性错误阻止了我犯下真正的错误的情况。每次问题都在于错误的const声明(在我的经验中最常见的情况是应该是const方法而没有声明为const)。 - 6502
但是这绝对没有意义,拥有非const元素的const向量却不能修改它们(表现得像const元素的const向量)。 - Nuclear
2
这个答案是误导性的:访问const向量的非const元素的问题可以通过迭代器(另一个答案)解决,这证明了这是std::vector的问题,而不是语言的问题。 - Steed
@Nuclear:如果您仔细选择约定,这就是完美的解决方案。my_vector<T> const 可以表示“不可调整大小、可重新分配”,my_vector<T const> 则表示“可调整大小、不可重新分配”,而 my_vector<T const> const 将是“不可调整大小、不可重新分配”。这已经是 unique_ptr<T> 的行为方式了 - 他们只是在 std::vector 上设计错误了,现在我们被困在其中。 - Eric

2
这可能是指C++14中的std::dynarray
实际上,如果大小在编译时固定,则可以使用std::array。但通常更适用于嵌入式编程、缓冲区、矩阵等方面,因为通常直到运行时才知道所需大小或者希望其可配置。

2
std::dynarray听起来很有趣,但根据当前cppreference.com关于dynarray的页面,它不会出现在C++14中:“在审查了n3690的国家机构意见后,这个库组件被从C++14工作文件中投票分离成一个单独的技术规范。截至n3797,该容器不是草案C++14的一部分。” - Alan

1
如果您能够修改IndexCoeffPair_t,您可以添加一些const成员函数,并使用它们通过使用mutable关键字来更改其某些成员。不过这有点像一个hack,因为现在您将能够更改任何const IndexCoeffPair_t的内容。
例如:
class IndexCoeffPair_t {
public:
    void changeX(int newVal) const {
        x = newVal;
    }

private:
    mutable int x;
};

1
这样做完全违背了“const”关键字的初衷。如果某物被声明为“const”却毫无意义,那还有什么意义呢... :) - UncleBens
一般情况下,应该避免使用mutable,这就是为什么我说它是一种hack方法。然而,由于原帖作者想要向量是const的,但是包含的对象不是,这个方法可以解决这个具体问题。 - MahlerFive
在我看来,这是更大的恶劣行为。一个令人担忧的事情是潜在的未来误用单个函数(你有多少次不小心调用了 push_back?),另一件事是说所有用户都可以修改常量对象的可观察状态。 - UncleBens
一种妥协的方法是创建一个Mutable<T>,就像我在下面的答案中所做的那样,而不是使T本身可变。 - Eric

1
这是MahlerFive回答的通用版本:

template<typename T>
class Mutable {
    mutable T m_val;
public:
    constexpr Mutable(T const& val) : m_val(val) { }
    constexpr Mutable(T&& val) : m_val(val) { }

    // note: all member functions are `const`
    constexpr Mutable const& operator=(T const& val) const {
        m_val = val;
        return *this;
    }
    constexpr Mutable const& operator=(T&& val) const {
        m_val = val;
        return *this;
    }

    constexpr operator T&() const {
        return m_val;
    }
};

你可以在代码中使用 std::vector<Mutable<T>> const,它的行为大多符合预期。

0

你可以尝试创建const std::vector<YouType*>。这样你就无法更改向量,但是你可以更改向量内的对象。 但要准确,因为你将修改原始对象而不是副本。

根据你的用例使用智能指针或裸指针:你拥有向量还是只有观察者向量。


1
“但是你可以更改向量内部的对象” - 你错了。虽然我希望它能够这样工作,但vector并不是这样设计的,所以它不会这样工作。 - Eric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接