我们假设我有一些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()
以更好地表明这些方法的实现比基于范围的循环更复杂 - 因此希望避免复制粘贴实现。
答案 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
可以是Storage
或const 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.
}
}