【问题标题】:range based for loop with const shared_ptr<>使用 const shared_ptr<> 基于范围的 for 循环
【发布时间】:2016-04-03 14:59:50
【问题描述】:

我有一个带有shared_ptr&lt;&gt; 的容器,例如一个vector&lt;shared_ptr&lt;string&gt;&gt;v,我想迭代v,表示常量。

这段代码:

vector<shared_ptr<string>> v;
v.push_back(make_shared<std::string>("hallo"));
...

for (const auto &s : v) {
    *s += ".";   // <<== should be invalid
}

看起来像我想要做的(表明sconst)但当然它不会使字符串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&lt;const string&gt;,则该示例有效。在我看来,这是一个很好的权衡,但这样指针会被复制,这在代码少、容器大的循环中可能会很耗时..

【问题讨论】:

  • for (shared_ptr&lt;const string&gt; s : v) { *s += "."; } 有什么问题?
  • 在我看来,您的 Something like 示例正是您所需要的,那么您的问题是什么?
  • @cpplearner 不行,是主要缺陷。
  • @Lightness 第一个例子说*s += "."; // &lt;&lt;== should be invalid,而这正是第二个例子实现的,它使s指向const string。这不是他要找的吗?
  • 我在for() 行中收到main.cpp:14: error: invalid initialization of reference of type 'std::shared_ptr&lt;const std::basic_string&lt;char&gt; &gt;&amp;' from expression of type 'std::shared_ptr&lt;std::basic_string&lt;char&gt; &gt;' 错误 - 但我看到了问题:我一直在声明一个引用shared_ptr&lt;const string&gt; &amp;s

标签: c++ pointers iteration constants container-data-type


【解决方案1】:

这就是答案。

但首先,布道:

指针和它指向的东西是两个独立的对象。任何一个,都不是,或者两者都可能是 const,而 const 指针仅仅意味着它不会指向不同的东西。如果指针是 const,则不能通过(可能是非 const)指针更改对象。

话虽如此,我们(我)经常编写使用unique_ptrshared_ptr 作为pimpl 的值语义包装对象。通常我们希望将包装器的 constness 传播到 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 类的完整示例,它在内部使用 shared_ptr 并正确传播 constness。

#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

【讨论】:

  • +1 表示propagate_const 提示,但您的回答对我来说似乎有点冗长。不过,对于更大的 API,这可能是一种方法..
  • @frans 谢谢。一个人永远不知道答案能走多远,所以万一其他人问同样的问题,我想提供一个扩展的答案,以防人们感兴趣。
【解决方案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 函数,编译器可能会内联它,所以这里没有性能损失。

【讨论】:

    【解决方案3】:

    这是众所周知的 C++ 限制,但有些人并不认为这是限制。

    您想迭代 constly,但不可变指针并不意味着不可变指针。

    shared_ptr&lt;string&gt; 类型和 shared_ptr&lt;const string&gt; 类型实际上是不相关的。

    选项 1

    for (const auto& ptr : v) {
        const auto& s = *ptr;
    
        s += ".";   // <<== is invalid
    }
    

    选项 2

    只是不要修改它。

    【讨论】:

    • 这些类型并不像你说的那么不相关。如果你真的想要,你可以写:for (const auto&amp; p : std::vector&lt;std::shared_ptr&lt;const std::string&gt;&gt;(v.begin(), v.end()) { /* ... */ }
    • @KerrekSB:当然,它们之间存在转换路径。就像您可以从 int 转换为 float 一样。但是这两者仍然是不同的类型,并且区别超出了 cv-qualification。就像这里一样。
    • 如果ptr 为空,会不会导致未定义的行为?
    • 由于似乎没有不复制指针的单一语句方法,您的选项 1 似乎最接近我正在寻找的内容。所以我把这个作为答案。不过,这两个陈述和大卫指出的风险给人留下了不好的印象……
    • @DavidHaim:是的,但不比原始代码更多。
    猜你喜欢
    • 1970-01-01
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    • 2014-12-06
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 2013-01-04
    相关资源
    最近更新 更多