有没有办法将std :: vector <const t * =“”>转换为std :: vector <t *>而无需额外的分配?

时间:2016-06-11 13:01:41

标签: c++ c++11 vector stl

我们假设我有一些Storage的{​​{1}}个Object,它有一个聚合指向某个向量中Objects的指针的方法。像这样:

class Storage
{
public:
   std::vector<Object*> aggregate_some_objects(); // non-const version
   std::vector<const Object*> aggregate_some_objects() const; // const version

private:
   std::unordered_map<size_t, Object> m_objects; // data is stored 
                                                 // by-value in a non-vector container
}

通常,在{+ 1}}的帮助下,可以通过在另一个内部调用其中一个来实现const +非const方法对,从而避免复制粘贴。然而,这是不可能的,因为方法的返回类型是不同的。

这里避免复制粘贴的最简单方法是从非const_cast版本调用const版本,并使用返回的const填充单独的std::vector<const T*> }。但是,这将导致至少2个堆分配(每个向量一个)。我想避免与第二个向量相关的分配。

我想知道是否有办法写出像

这样的东西
std::vector<T*>

因此,允许写

template <typename T>
std::vector<T*> remove_const_from_vector_of_ptrs(std::vector<const T*>&& input)
{
   std::vector<T*> ret;
   // do some magic stuff here that does not involve
   // more memory allocations
   return ret;
}

没有&#39;发布&#39; std::vector<const Object*> Storage::aggregate_some_objects() const { // non-trivial implementation } std::vector<Object*> Storage::aggregate_some_objects() { auto objects = const_cast<const Storage*>(this)->aggregate_some_objects(); return remove_const_from_vector_of_ptrs(std::move(objects)); } 中的方法(例如std::vector)允许转移内存所有权 - 这是一个很好的理由,所以我希望这是不可能的。

我也明白,如果可能的话,通常应该避免危险的操作,就像std::unique_ptr一样。但是在这种情况下仔细使用似乎比复制粘贴更有用。

编辑:添加说明我的意思是&#39;额外&#39;分配并将const_cast更改为Storage::aggregate_objects()以更好地表明这些方法的实现比基于范围的循环更复杂 - 因此希望避免复制粘贴实现。

4 个答案:

答案 0 :(得分:13)

简短的回答是:不。 std::vector<Object*>std::vector<const Object*>是两个不同的独立类。它们彼此不同,因为class A来自class B。通常认为只是因为它们都以std::vector开头,它们彼此之间有某种联系。事实并非如此,因此没有办法将一个转换为另一个,#34;就位#34;。这些向量类中的每一个都拥有相应的内部data(),并且不会愿意将它放到其他一些奇怪的类中。

答案很长仍然没有,但在许多情况下,可以解决这个问题,以避免手动代码重复。事实上,在大多数情况下,代码重复是不可避免的,最好的办法是避免手动代码重复。

一种常见的方法是使常量和可变类方法成为单个共享私有模板的外观:

// Header file:

class Storage {

public:

    std::vector<const Object*> aggregate_objects() const;

    std::vector<Object*> aggregate_objects();

private:

    template<typename v_type> void make_aggregate_objects(v_type &v) const;
};

// In the translation unit:

template<typename v_type> void Storage::make_aggregate_objects(v_type &v) const
{
     // Now, create 'v' here... v.reserve(), v.push_back(), etc...
}

std::vector<const Object*> Storage::aggregate_objects() const
{
     std::vector<const Object *> v;

     make_aggregate_objects(v);

     return v;
}

std::vector<Object*> Storage::aggregate_objects()
{
     std::vector<const Object *> v;

     make_aggregate_objects(v);

     return v;
}

编译器仍然会生成两个几乎相同的代码块,但至少不是你在做所有的输入。

另一种类似的方法是将lambda传递给模板函数而不是传递一个vector对象,私有模板函数使用lambda函数作为回调来构造返回的向量。通过一些类型擦除和std::function的一些帮助,私有类方法可以转换为普通方法,而不是模板方法。

答案 1 :(得分:2)

在没有重新分配内存和复制指针的情况下,无法将std::vector<const Object*>转换为std::vector<Object*>,因为std::vector是一个容器并拥有其内存。

在这种情况下使用reinterpret_cast可能有效,但是未定义的行为并取决于std::vector的实现:

std::vector<const Object*> const_vec = ...;
std::vector<Object*>& vec = reinterpret_cast<std::vector<Object*>&>(const_vec);

避免const_cast或不必要的分配的解决方案将是第三个模板化函数:

template<typename Stor>
static auto Storage::aggregate_objects_(Stor&)
-> std::vector<std::conditional_t<std::is_const<Stor>::value, const Object*, Object*>>
{
    ...
}

其中Stor可以是Storageconst Storage

然后aggregate_objects()将实现为:

std::vector<const Object*> Storage::aggregate_objects() const {
    return aggregate_objects_(*this);
}

std::vector<Object*> Storage::aggregate_objects() {
    return aggregate_objects_(*this);
}

答案 2 :(得分:1)

您的函数按值返回,因此总是分配 - 什么&#34;额外分配&#34;你在说什么?

如果您只是在内部存储vector<Object*>,那么解决您的问题非常简单:

std::vector<Object*> Storage::aggregate_objects()
{ return m_data; };

std::vector<const Object*> Storage::aggregate_objects() const
{ return std::vector<const Object*>(m_data.begin(), m_data.end()); }

修改以回复您的更新问题:

为避免复制和粘贴函数体,不应编写错误的代码!

没有必要复制函数体,或者编写带有危险或冒险演员的坏代码,只需使用两个函数调用的模板,正如Sam Varshavchik的答案所示。

答案 3 :(得分:0)

显而易见的答案是否定的,std::vector<T*>std::vector<const T*>是2个不能互换的不同对象。

但是,凭借std::vector内部人员的知识,我们可以很容易地看到我们将T*const T*存储在std::vector内,这无关紧要从它的角度来看。

因此,一种方法是将结果从一种类型强制转换为另一种类型。所以:

template <typename T>
std::vector<T*> remove_const_from_result(std::vector<const T*>&& input) {
  return reinterpret_cast<std::vector<T*>&>(input);
}

这种方法的一个重要警告是,只有当我们对我们强制施放的容器内部相当自信时,我们才能做到这一点。

请注意,这仍然无法解决您的原始问题,即您有2个不同的成员函数,并且您基本上const_cast - 将结果转换为另一个。

为了说明这一点,我假设你有一个没有std::vector的getter函数,所以请这样做:

struct Foo {
  const Bar* bar() const { return &bar_; }
  Bar* bar() { return const_cast<Bar*>(bar()); }

 private:
  Bar bar_;
};

IMO的一个更好的方法是在内部公开一个模板化的函数,然后使用它。因此,Foo变为:

struct Foo {
  const Bar* bar() const { return get_internal<const Bar*>(&bar_); }
  Bar* bar() { return get_internal<Bar*>(&bar_); }

 private:
  Bar bar_;
  template <typename T>
  T get_internal(std::remove_const<T>::type ptr) const { return ptr; }
};

因此,类似地,对于您的示例,您可以使用相同的方法:

struct Storage {
  std::vector<const Object*> aggregate_objects() const { return aggregate_internal<const Object*>(); }
  std::vector<Object*> aggregate_objects() { return aggregate_internal<Object*>(); }

 private:
  template <typename T>
  std::vector<T*> aggregate_internal() const {
    // actual aggregate function where T* can be const T* also.
  }
}