【问题标题】:Expose the const and nonconst versions of begin() and end() to iterate member vector with smart pointer公开 begin() 和 end() 的 const 和 nonconst 版本以使用智能指针迭代成员向量
【发布时间】:2014-09-28 23:26:39
【问题描述】:

//猫.h

class Cat
{public:
    void const_meow() const{ ... };
    void meow(){ ... };
};

class CatLibrary
{public:
    std::vector<std::shared_ptr<Cat>>::iterator begin()
    { 
        return m_cat_list.begin();
    }

    // compile error, the compiler complains cannot covert type
    //     from `std::vector<std::shared_ptr<Cat>>::const_iterator` 
    //     to   `std::vector<std::shared_ptr<const Cat>>::const_iterator`
    std::vector<std::shared_ptr<const Cat>>::const_iterator begin() const 
    { 
        return m_cat_list.cbegin();
    }
private:
    std::vector<std::shared_ptr<Cat>> m_cat_list;
};

// main.cpp

CatLibrary cat_library;       
cat_library.add(std::make_shared<Cat>());
cat_library.add(std::make_shared<Cat>());

for(auto& cat: cat_library )
{
   cat->const_meow(); 
   cat->meow(); 
}       
for(const auto& cat: cat_library)
{
   cat->const_meow(); 
   cat->meow();       // hope to compile error due to invoking non_const method of Cat.
}


const CatLibrary& const_cat_library = cat_library;        
for(auto& cat: const_cat_library ) 
{
   cat->const_meow(); 
   cat->meow();       // hope to compile error due to invoking non_const method of Cat.
}
const CatLibrary& const_cat_library = cat_library; 

for(const auto& cat: const_cat_library ) 
{
   cat->const_meow(); 
   cat->meow();       // hope to compile error due to invoking non_const method of Cat.
}

我希望我的 CatLibrary 公开 non-const begin()non-const end(),客户端可以在其中迭代指向可变 Cat 的智能指针。 const begin()const end() 返回指向不可变的迭代器。

那么当客户端迭代 const CatLibrary 时,我不担心他会修改库中 Cat 的内容。

但是添加到我的成员函数begin() 中的const 仅将指针限定为 const 指针,而不是它指向的 Cat。

在不涉及指针的情况下,具有常量的向量使迭代器也指向具有常量的元素。但我希望这个效果也适用于智能指针指向的元素。

我有办法解决我的问题,但我不确定将来使用会出现什么问题。

在 const 和 nonconst 中维护两个向量

#include <iostream>
#include <memory>
#include <vector>

class Cat
{public:
    void const_meow() const { std::cout << "meow" << std::endl;}
    void meow() { std::cout << "meow" << std::endl;}
};


class CatLibrary
{public:

    void add(std::shared_ptr<Cat> cat)
    {
        m_cat_list.push_back(cat);
        m_cat_const_list.push_back(cat);
    };

    std::vector<std::shared_ptr<Cat>>::const_iterator begin()
    { 
        return m_cat_list.begin();
    }

    std::vector<std::shared_ptr<const Cat>>::const_iterator begin() const
    { 
        return m_cat_const_list.begin();
    }

    std::vector<std::shared_ptr<Cat>>::const_iterator end()
    { 
        return m_cat_list.end();
    }

    std::vector<std::shared_ptr<const Cat>>::const_iterator end() const
    { 
        return m_cat_const_list.end();
    }


private:
    std::vector<std::shared_ptr<Cat>> m_cat_list;
    std::vector<std::shared_ptr<const Cat>> m_cat_const_list;
};


int main()
{
   CatLibrary cat_library;

   cat_library.add(std::make_shared<Cat>());
   cat_library.add(std::make_shared<Cat>());
   cat_library.add(std::make_shared<Cat>());

   const CatLibrary& const_cat_library = cat_library;
   for(auto& cat: cat_library)
   {
      cat->meow();
   }

   return 0;
}

或者有没有另一种解决方案来解决向量中智能指针的这种常量问题?

【问题讨论】:

  • 我想我会在迭代期间使用类似 boost 的 transform_iterator 来进行转换,而不是维护两个向量。

标签: c++ constants smart-pointers const-iterator


【解决方案1】:

我玩了一点 boost::transform_iterator ,似乎可以实现你想要的,尽管我发现结果并不令人满意。我猜 DanDan 建议不要向用户公开实现细节可能是正确的方法。

尽管如此,我还是将 boost::transform_iterator 作为参考:

#include <boost/iterator/transform_iterator.hpp>

class CatLibrary
{
public:
  typedef std::vector<std::shared_ptr<Cat>> cat_list_t;
  typedef std::function<const Cat *(const std::shared_ptr<Cat>&)> transform_t;
  typedef boost::transform_iterator<transform_t,
                                    cat_list_t::const_iterator> const_iterator;  

[...]

  const_iterator begin() const {
    return const_iterator(std::begin(m_cat_list),
                          [](const std::shared_ptr<Cat>& c) {
                            return static_cast<const Cat *>(c.get());
                          });
  }
  const_iterator end() const { // same as above ... }
};

使用const_cat_library 的for 循环中cat 的类型现在是const Cat*,因此不允许调用非常量函数。使用g++ -c --std=c++11 transform_iterator.cxx -Wall -I$BOOSTINC(gcc 版本 4.8.1)编译会产生以下错误:

error: invalid initialization of non-const reference of type 'const Cat*&' from an rvalue of type 'boost::[...]::reference {aka const Cat*}'
    for(auto& cat: const_cat_library ) {
                   ^
//twice:
error: passing 'const Cat' as 'this' argument of 'void Cat::meow()' discards qualifiers [-fpermissive]
    cat->meow();       // hope to compile error due to invoking non_const method of Cat.
              ^

可能出现的许多问题之一是 shared_ptr 被绕过,用户可以删除循环内的Cat 对象。

【讨论】:

    【解决方案2】:

    我实际上会考虑转变您的抽象概念并封装您的猫收藏。迭代器和可变对象是实现细节。

    所以改用 cat 编写函数:

    PrintCats()
    PlayWithCats()
    

    如果您的猫库不知道您要对它们执行的操作,您可以查看传入函数指针。 boost::function 对此有好处。 你会有这样的功能

    void CatLibrary::DoStuffToCats(boost::function<void, (const Cat&)> f))
    {
        std::foreach(m_cat_list.begin(), m_cat_list.end(), f);
    }
    

    【讨论】:

      【解决方案3】:

      在您发布的示例中,不需要 begin() 和 end() 的 const 版本。您可以在基于范围的 for 循环中使用 const 引用,而无需这些函数的 const 版本,并且您根本不需要 cat 是 const auto& 来调用 const 成员函数。

      如果您的 cat_library 对象本身是 const,则可能需要 const begin() 和 end(),但是您不能添加这样的项目。

      【讨论】:

      • 但是如果我有一个像void dump_cat(const CatLibrary&amp; cat_library){ for(auto&amp; cat : cat_library){...}; }这样的只读函数,那么我传入的cat_library就是一个const变量。
      • 是的,没错,那么您确实需要 const begin() 和 end()。你可以使用这个: std::vector<:shared_ptr>>::const_iterator begin() const { return m_cat_list.begin(); } 允许迭代,虽然 cat 对象本身仍然是非常量并且可能会被修改,但我现在想不出一个简单的方法来防止这种情况。
      • 是的。如果不涉及指针,具有常量的向量将使迭代器也指向具有常量的元素。但我希望这种效果也适用于智能指针指向的元素。也许我会像 Flexo 所说的那样检查transform_iterator 。谢谢。
      猜你喜欢
      • 1970-01-01
      • 2020-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-28
      • 2021-08-06
      • 1970-01-01
      相关资源
      最近更新 更多