kov*_*rex 36 c++ const-correctness
让我们在一个示例中展示它,其中我们有一个包含主数据的 Data 类、某种指向主数据的索引,并且我们还需要公开索引const
的版本。
class Data
{
public:
const std::vector<int>& getPrimaryData() const { return this->primaryData; }
const std::vector<int*>& getIndex() const { return this->index; }
private:
std::vector<int> primaryData;
std::vector<int*> index;
};
Run Code Online (Sandbox Code Playgroud)
这是错误的,因为用户可以轻松修改数据:
const Data& data = something.getData();
const std::vector<int*>& index = data.getIndex();
*index[0] = 5; // oups we are modifying data of const object, this is wrong
Run Code Online (Sandbox Code Playgroud)
原因是 Data::getIndex 应返回的正确类型是:
const std::vector<const int*>&
Run Code Online (Sandbox Code Playgroud)
但是您可以猜测当您尝试以“仅将非常量变体转换为常量变体”的方式编写方法时会发生什么:
// compiler error, can't convert std::vector<int*> to std::vector<const int*> these are unrelated types.
const std::vector<const int*>& getIndex() const { return this->index; }
Run Code Online (Sandbox Code Playgroud)
据我所知,C++对于这个问题还没有什么好的解决方案。显然,我可以创建新的向量,从索引复制值并返回它,但从性能角度来看这没有任何意义。
请注意,这只是大型程序中实际问题的简化示例。int 可以是一个更大的对象(比如说 Book),而 index 可以是某种书籍的索引。而Data可能需要使用索引来修改书籍,但同时提供索引以const的方式读取书籍。
康桓瑋*_*康桓瑋 50
在 C++20 中,您可以只返回 astd::span
类型的元素const int*
#include <vector>
#include <span>
class Data
{
public:
std::span<const int* const> getIndex() const { return this->index; }
private:
std::vector<int*> index;
};
int main() {
const Data data;
const auto index = data.getIndex();
*index[0] = 5; // error: assignment of read-only location
}
Run Code Online (Sandbox Code Playgroud)
Ser*_*sta 19
每种语言都有其规则和用法...std::vector<T>
并且std::vector<const T>
在 C++ 中是不同的类型,不可能将一种语言 const_cast 转换为另一种语言,句号。这并不意味着常量被破坏,只是意味着它不是它的工作方式。
对于使用部分,返回完整的容器通常被视为一种糟糕的封装实践,因为它使实现可见并将其与接口联系起来。最好有一个方法接受一个索引并返回一个指向 const 的指针(或者如果需要的话返回一个对 const 指针的引用):
const int* getIndex(int i) const { return this->index[i]; }
Run Code Online (Sandbox Code Playgroud)
这是可行的,因为 aT*
可以被 const_casted 为 a const T *
。
如果您可以使用 C++20 或更高版本(或 GSL 等库),那么使用范围或跨度的最佳答案是一个很好的解决方案。如果没有,这里有一些其他方法。
\n#include <vector>\n\nclass Data\n{\npublic:\n const std::vector<const int>& getPrimaryData() const\n {\n return *reinterpret_cast<const std::vector<const int>*>(&primaryData);\n }\n\n const std::vector<const int* const>& getIndex()\n {\n return *reinterpret_cast<const std::vector<const int* const>*>(&index);\n }\n\nprivate:\n std::vector<int> primaryData;\n std::vector<int*> index;\n};\n
Run Code Online (Sandbox Code Playgroud)\n这是危险的生活。这是未定义的行为。至少,你不能指望它是便携式的。没有什么可以阻止实现创建不同的模板重载const std::vector<int>
,const std::vector<const int>
这会破坏您的程序。例如,一个库可能会private
向 a vector
of 非元素添加一些额外的数据成员,而对于 a of元素const
,它不会\xe2\x80\x99t (无论如何都不鼓励这样做)。vector
const
虽然我还没有\xe2\x80\x99t对此进行了广泛的测试,但它似乎可以在GCC、Clang、ICX、ICC和MSVC中工作。
\n智能指针的数组专门化允许从std::shared_ptr<T[]>
到std::shared_ptr<const T[]>
或进行转换std::weak_ptr<const T[]>
。您也许可以使用std::shared_ptr
作为 的替代方案std::vector
和std::weak_ptr
的视图的替代方案vector
。
#include <memory>\n\nclass Data\n{\npublic:\n std::weak_ptr<const int[]> getPrimaryData() const\n {\n return primaryData;\n }\n\n std::weak_ptr<const int* const[]> getIndex()\n {\n return index;\n }\n\nprivate:\n std::shared_ptr<int[]> primaryData;\n std::shared_ptr<int*[]> index;\n};\n
Run Code Online (Sandbox Code Playgroud)\n与第一种方法不同,这是类型安全的。与范围或跨度不同,它自 C++11 起就可用。请注意,您实际上并不想返回没有数组绑定的不完整类型\xe2\x80\x94,\xe2\x80\x99s 只是乞求缓冲区溢出漏洞\xe2\x80\x94,除非您的客户端通过以下方式知道数组的大小其他一些手段。它主要对固定大小的数组有用。
\n一个很好的替代方案std::span
是 a std::ranges::subrange
,您可以专门研究const_iterator
数据的成员类型。这是根据开始和结束迭代器定义的,而不是迭代器和大小,甚至可以用于(经过修改)具有非连续存储的容器。
这适用于 GCC 11 和 clang 14 with -std=c++20 -stdlib=libc++
,但不适用于所有其他编译器(截至 2022 年):
#include <ranges>\n#include <vector>\n\nclass Data\n{\nprivate:\n using DataType = std::vector<int>;\n DataType primaryData;\n using IndexType = std::vector<DataType::pointer>;\n IndexType index;\n\npublic:\n /* The types of views of primaryData and index, which cannot modify their contents.\n * This is a borrowed range. It MUST NOT OUTLIVE the Data, or it will become a dangling reference.\n */\n using DataView = std::ranges::subrange<DataType::const_iterator>;\n // This disallows modifying either the pointers in the index or the data they reference.\n using IndexView = std::ranges::subrange<const int* const *>;\n\n /* According to the C++20 standard, this is legal. However, not all\n * implementations of the STL that I tested conform to the requirement that\n * std::vector::cbegin is contstexpr.\n */ \n constexpr DataView getPrimaryData() const noexcept\n {\n return DataView( primaryData.cbegin(), primaryData.cend() );\n }\n\n constexpr IndexView getIndex() const noexcept\n {\n return IndexView( index.data(), index.data() + index.size() );\n }\n};\n
Run Code Online (Sandbox Code Playgroud)\n您可以定义DataView
为实现范围接口的任何类型,例如 astd::span
或std::string_view
,并且客户端代码应该仍然可以工作。
您可以将变换视图返回到向量。例子:
auto getIndex() const {
auto to_const = [](int* ptr) -> const int* {
return ptr;
};
return this->index | std::views::transform(to_const);
}
Run Code Online (Sandbox Code Playgroud)
编辑:std::span
是更简单的选项。
如果index
包含指向 的元素的指针primaryData
,那么您可以通过存储表示当前指向对象的索引的整数来解决问题。任何有权访问非常量的人都primaryData
可以轻松地将这些索引转换为指向非常量的指针,而其他人则不能。
primaryData
不稳定,
如果primaryData
不稳定,并且index
包含指向 的指针primaryData
,那么当前的设计就会被破坏,因为这些指针将失效。只要索引保持稳定(即仅插入到后面),整数索引替代方案就可以解决此问题。如果甚至索引也不稳定,那么您使用的数据结构是错误的。链表和链表的迭代器向量可以工作。
你要求的是std::experimental::propagate_const
. 但由于它是一个实验性功能,因此不能保证任何特定的工具链都会附带实现。您可以考虑实施自己的。然而,有一个MIT 许可的实现。包含标题后:
using namespace xpr=std::experimental;
///...
std::vector<xpr::propagate_const<int*>> my_ptr_vec;
Run Code Online (Sandbox Code Playgroud)
但请注意,原始指针被认为是邪恶的,因此您可能需要使用std::unique_ptr
or std::shared_ptr
。propagate_const
应该接受智能指针和原始指针类型。
正如评论中提到的,你可以这样做:
\nclass Data\n{\npublic:\n const std::vector<int>& getPrimaryData() const { return this->primaryData; }\n const std::vector<const int*>& getIndex() const { return this->index; }\nprivate:\n std::vector<int> primaryData;\n std::vector<const int*> index;\n int* read_index_for_writing(std::size_t i) { return const_cast<int*>(index[i]); }\n};\n
Run Code Online (Sandbox Code Playgroud)\n该解决方案的优点是:它在标准的每个版本和每个合规实施中都有效且安全。它返回一个向量引用,没有有趣的包装类 \xe2\x80\x93 ,这对调用者来说可能并不重要,但它可能。
\n不好:您必须在内部使用辅助方法,尽管仅在出于写入数据的目的而读取索引时。评论者将其描述为“肮脏”,但对我来说似乎足够干净。
\n