基于范围的 for 循环与 const shared_ptr<>
range based for loop with const shared_ptr<>
我有一个装有 shared_ptr<>
的容器,例如a vector<shared_ptr<string>>
v
并且我想遍历 v
以指示 const-ness。
此代码:
vector<shared_ptr<string>> v;
v.push_back(make_shared<std::string>("hallo"));
...
for (const auto &s : v) {
*s += "."; // <<== should be invalid
}
看起来就像我想做的(表明s
是const
)但是当然它不会使字符串const
.
是否有一种优雅的方法来遍历 shared_ptr
的容器,明确表示内容不会被修改?
类似
for (shared_ptr<const string> s : v) {
*s += "."; // <<== will not compile
}
(但由于其他原因此代码无法编译:))
编辑:
我错了。最初我是在声明一个引用,这导致编译器错误
for (shared_ptr<const string> &s : v) { // <<== does not compile
...
}
如果您声明 shared_ptr<const string>
,则该示例有效。在我看来,这是一个很好的权衡,但这种方式会复制指针,这在代码很少和容器很大的循环中可能很耗时..
这是 C++ 的 well-known 限制,有些人认为这不是限制。
您想 const
迭代,但不可变指针并不意味着不可变指针。
类型 shared_ptr<string>
和类型 shared_ptr<const string>
实际上是无关的。
选项 1
for (const auto& ptr : v) {
const auto& s = *ptr;
s += "."; // <<== is invalid
}
选项 2
只是不要修改它。
我会使用模板方法
template <class T,class F>
void forEach(const std::vector<std::shared_ptr<T>>& vec, F&& f){
for (const auto& ptr : vec){
if (ptr){
f(std::cref(*ptr));
}
}
}
我在那里放了一个 lambda 函数,编译器可能无论如何都会内联它,所以这里没有性能损失。
答案在这里。
但首先是布道:
一个指针和它指向的东西是两个独立的对象。 none 或者两者都可以是 const 并且 const 指针仅仅意味着它不会指向不同的东西。如果指针对象是const,对象可能不会通过(可能non-const)指针改变。
话虽如此,我们 (I) 经常编写使用 unique_ptr
或 shared_ptr
作为 pimpl 的 value-semantic 包装器对象。通常我们希望将包装器的常量传播到 impl。
我相信 c++17 会用它的 propagate_const
指针包装器解决这个问题。
与此同时,构建您自己的平台非常简单:
#include <iostream>
#include <type_traits>
#include <memory>
#include <string>
#include <vector>
namespace traits
{
template<class T> struct pointee;
template<class T, class D>
struct pointee<std::unique_ptr<T, D>> {
using type = T;
};
template<class T>
struct pointee<std::shared_ptr<T>> {
using type = T;
};
template<class T> using pointee_t = typename pointee<T>::type;
}
template<class PointerType>
struct propagate_const
{
using pointer_type = PointerType;
using element_type = traits::pointee_t<pointer_type>;
using value_type = std::decay_t<element_type>;
using reference = value_type&;
using const_reference = const value_type&;
propagate_const(pointer_type p) : _ptr(std::move(p)) {}
const_reference operator*() const {
return *_ptr;
}
auto operator*()
-> std::enable_if_t<not std::is_const<element_type>::value, reference>
{
return *_ptr;
}
private:
pointer_type _ptr;
};
template<class PointerType>
auto make_propagating_pointer(PointerType&& p)
{
return propagate_const<PointerType>(std::forward<PointerType>(p));
}
int main()
{
using namespace std;
vector<propagate_const<shared_ptr<string>>> v;
v.emplace_back(make_shared<string>("hello"));
for (const auto& p : v)
{
// *p += " there"; // compile error
cout << *p;
cout << endl;
}
for (auto& p : v)
{
*p += " there";
cout << *p;
cout << endl;
}
return 0;
}
预期输出:
hello
hello there
这个非常简单,仅支持 operator*
但添加一整套运算符是微不足道的。请注意,当指针为 const 时,我禁用了可变访问。
参考:http://en.cppreference.com/w/cpp/experimental/propagate_const
为了好玩,这里有一个 shared_string
class 的完整示例,它在内部使用 shared_ptr
并正确传播常量。
#include <iostream>
#include <type_traits>
#include <memory>
#include <string>
#include <vector>
template<class PointerType>
struct propagate_const
{
using pointer_type = PointerType;
using element_type = std::remove_reference_t<decltype(*std::declval<PointerType&>())>;
using reference = element_type&;
using const_reference = const element_type&;
propagate_const(pointer_type p) : _ptr(std::move(p)) {}
const_reference operator*() const {
return *_ptr;
}
auto operator*()
-> std::enable_if_t<not std::is_const<element_type>::value, reference>
{
return *_ptr;
}
private:
pointer_type _ptr;
};
template<class PointerType>
auto make_propagating_pointer(PointerType&& p)
{
return propagate_const<PointerType>(std::forward<PointerType>(p));
}
struct shared_string
{
shared_string(std::string s) : _impl(std::make_shared<std::string>(std::move(s))) {};
shared_string(std::shared_ptr<std::string> sp) : _impl(sp) {};
shared_string(propagate_const<std::shared_ptr<std::string>> sp) : _impl(sp) {};
auto& operator += (const std::string& s) {
*_impl += s;
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const shared_string& ss) {
return os << *(ss._impl);
}
private:
propagate_const<std::shared_ptr<std::string>> _impl;
};
template<class T, std::enable_if_t<std::is_const<T>::value>* = nullptr >
std::string check_const(T&)
{
return std::string("const");
}
template<class T, std::enable_if_t<not std::is_const<T>::value>* = nullptr >
std::string check_const(T&)
{
return std::string("not const");
}
int main()
{
using namespace std;
// a vector of mutable shared_strings
vector<shared_string> v;
// a vector of immutable shared_strings
vector<const shared_string> cv;
// make a shared_string
v.emplace_back(make_shared<string>("hello"));
// refer to the *same one* in cv
cv.emplace_back(v[0]);
for (const auto& p : v)
{
// *p += " there"; // immutable reference to mutable shared string - not allowed
cout << check_const(p) << " " << p;
cout << endl;
}
for (auto& p : v)
{
cout << check_const(p) << " " << p;
p += " there"; // mutable reference to mutable shared string - allowed
cout << " becomes " << p;
cout << endl;
}
for (auto&p : cv)
{
cout << check_const(p) << " " << p;
// p += " world"; // p is actually immutable because cv contains immutable objects
cout << endl;
}
return 0;
}
预期输出:
const hello
not const hello becomes hello there
const hello there
我有一个装有 shared_ptr<>
的容器,例如a vector<shared_ptr<string>>
v
并且我想遍历 v
以指示 const-ness。
此代码:
vector<shared_ptr<string>> v;
v.push_back(make_shared<std::string>("hallo"));
...
for (const auto &s : v) {
*s += "."; // <<== should be invalid
}
看起来就像我想做的(表明s
是const
)但是当然它不会使字符串const
.
是否有一种优雅的方法来遍历 shared_ptr
的容器,明确表示内容不会被修改?
类似
for (shared_ptr<const string> s : v) {
*s += "."; // <<== will not compile
}
(但由于其他原因此代码无法编译:))
编辑:
我错了。最初我是在声明一个引用,这导致编译器错误
for (shared_ptr<const string> &s : v) { // <<== does not compile
...
}
如果您声明 shared_ptr<const string>
,则该示例有效。在我看来,这是一个很好的权衡,但这种方式会复制指针,这在代码很少和容器很大的循环中可能很耗时..
这是 C++ 的 well-known 限制,有些人认为这不是限制。
您想 const
迭代,但不可变指针并不意味着不可变指针。
类型 shared_ptr<string>
和类型 shared_ptr<const string>
实际上是无关的。
选项 1
for (const auto& ptr : v) {
const auto& s = *ptr;
s += "."; // <<== is invalid
}
选项 2
只是不要修改它。
我会使用模板方法
template <class T,class F>
void forEach(const std::vector<std::shared_ptr<T>>& vec, F&& f){
for (const auto& ptr : vec){
if (ptr){
f(std::cref(*ptr));
}
}
}
我在那里放了一个 lambda 函数,编译器可能无论如何都会内联它,所以这里没有性能损失。
答案在这里。
但首先是布道:
一个指针和它指向的东西是两个独立的对象。 none 或者两者都可以是 const 并且 const 指针仅仅意味着它不会指向不同的东西。如果指针对象是const,对象可能不会通过(可能non-const)指针改变。
话虽如此,我们 (I) 经常编写使用 unique_ptr
或 shared_ptr
作为 pimpl 的 value-semantic 包装器对象。通常我们希望将包装器的常量传播到 impl。
我相信 c++17 会用它的 propagate_const
指针包装器解决这个问题。
与此同时,构建您自己的平台非常简单:
#include <iostream>
#include <type_traits>
#include <memory>
#include <string>
#include <vector>
namespace traits
{
template<class T> struct pointee;
template<class T, class D>
struct pointee<std::unique_ptr<T, D>> {
using type = T;
};
template<class T>
struct pointee<std::shared_ptr<T>> {
using type = T;
};
template<class T> using pointee_t = typename pointee<T>::type;
}
template<class PointerType>
struct propagate_const
{
using pointer_type = PointerType;
using element_type = traits::pointee_t<pointer_type>;
using value_type = std::decay_t<element_type>;
using reference = value_type&;
using const_reference = const value_type&;
propagate_const(pointer_type p) : _ptr(std::move(p)) {}
const_reference operator*() const {
return *_ptr;
}
auto operator*()
-> std::enable_if_t<not std::is_const<element_type>::value, reference>
{
return *_ptr;
}
private:
pointer_type _ptr;
};
template<class PointerType>
auto make_propagating_pointer(PointerType&& p)
{
return propagate_const<PointerType>(std::forward<PointerType>(p));
}
int main()
{
using namespace std;
vector<propagate_const<shared_ptr<string>>> v;
v.emplace_back(make_shared<string>("hello"));
for (const auto& p : v)
{
// *p += " there"; // compile error
cout << *p;
cout << endl;
}
for (auto& p : v)
{
*p += " there";
cout << *p;
cout << endl;
}
return 0;
}
预期输出:
hello
hello there
这个非常简单,仅支持 operator*
但添加一整套运算符是微不足道的。请注意,当指针为 const 时,我禁用了可变访问。
参考:http://en.cppreference.com/w/cpp/experimental/propagate_const
为了好玩,这里有一个 shared_string
class 的完整示例,它在内部使用 shared_ptr
并正确传播常量。
#include <iostream>
#include <type_traits>
#include <memory>
#include <string>
#include <vector>
template<class PointerType>
struct propagate_const
{
using pointer_type = PointerType;
using element_type = std::remove_reference_t<decltype(*std::declval<PointerType&>())>;
using reference = element_type&;
using const_reference = const element_type&;
propagate_const(pointer_type p) : _ptr(std::move(p)) {}
const_reference operator*() const {
return *_ptr;
}
auto operator*()
-> std::enable_if_t<not std::is_const<element_type>::value, reference>
{
return *_ptr;
}
private:
pointer_type _ptr;
};
template<class PointerType>
auto make_propagating_pointer(PointerType&& p)
{
return propagate_const<PointerType>(std::forward<PointerType>(p));
}
struct shared_string
{
shared_string(std::string s) : _impl(std::make_shared<std::string>(std::move(s))) {};
shared_string(std::shared_ptr<std::string> sp) : _impl(sp) {};
shared_string(propagate_const<std::shared_ptr<std::string>> sp) : _impl(sp) {};
auto& operator += (const std::string& s) {
*_impl += s;
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const shared_string& ss) {
return os << *(ss._impl);
}
private:
propagate_const<std::shared_ptr<std::string>> _impl;
};
template<class T, std::enable_if_t<std::is_const<T>::value>* = nullptr >
std::string check_const(T&)
{
return std::string("const");
}
template<class T, std::enable_if_t<not std::is_const<T>::value>* = nullptr >
std::string check_const(T&)
{
return std::string("not const");
}
int main()
{
using namespace std;
// a vector of mutable shared_strings
vector<shared_string> v;
// a vector of immutable shared_strings
vector<const shared_string> cv;
// make a shared_string
v.emplace_back(make_shared<string>("hello"));
// refer to the *same one* in cv
cv.emplace_back(v[0]);
for (const auto& p : v)
{
// *p += " there"; // immutable reference to mutable shared string - not allowed
cout << check_const(p) << " " << p;
cout << endl;
}
for (auto& p : v)
{
cout << check_const(p) << " " << p;
p += " there"; // mutable reference to mutable shared string - allowed
cout << " becomes " << p;
cout << endl;
}
for (auto&p : cv)
{
cout << check_const(p) << " " << p;
// p += " world"; // p is actually immutable because cv contains immutable objects
cout << endl;
}
return 0;
}
预期输出:
const hello
not const hello becomes hello there
const hello there