有没有办法将std :: vector 转换为std :: vector 而无需额外分配?
比方说,我有一个Storage
的某些Object
s,它有一个方法可以汇总指向某个向量中某些Objects
的指针。就像这样:有没有办法将std :: vector <const T*>转换为std :: vector <T*>而无需额外分配?
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
}
通常,没有办法避免在执行常量+非const方法对复制粘贴通过调用其他内其中一人的const_cast
帮助。然而,这是不可能的,因为这些方法的返回类型是不同的。
最直接的方式,以避免在这里复制粘贴将来自非const
版本叫const
版本,并使用返回std::vector<const T*>
来填充一个单独的std::vector<T*>
。但是,这将导致至少2堆分配(每个向量一个)。我想避免与第二个向量相关联的分配。
我不知道是否有办法写类似
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;
}
因此,允许写
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
例如)没有“释放”的方法,使转移内存所有权 - 出于一个很好的理由,所以我认为这是不可能的。
我也明白,如果可能的话,这应该是一个危险的操作,应该被普遍避免,就像const_cast
一样。但是,像这样的情况下的谨慎使用似乎比复制粘贴更有用。
编辑:添加澄清说明,对我是什么“额外”的分配意味着,改变Storage::aggregate_objects()
到Storage::aggregate_some_objects()
更好地表明,这些方法的实现是比较复杂的,然后基于范围的循环 - 因此避免复制的愿望 - 实施。
简短的回答是:不。 std::vector<Object*>
和std::vector<const Object*>
是两个不同的独立类。它们彼此不同,因为class A
来自class B
。人们经常认为,仅仅因为他们都以std::vector
开头,他们在某种程度上是相互关联的。这是不正确的,因此没有办法将一个转换为另一个,“就地”。这些向量类中的每一个都拥有相应的内部对象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传递给模板函数,而不是将一个矢量对象与使用lambda函数作为回调函数的私有模板函数传递来构造返回的向量。通过一些类型的擦除,以及std::function
的一些帮助,私有类方法可以变成普通方法,而不是模板方法。
这似乎是最简单的解决方案最完整的答案。谢谢!只是想补充一点:尽管你把这种方法称为通用,但我想要指出的是,如果我遇到这样的代码,我可能会为了“v_type”被模板化而思考我的头脑。这是否打算与不同的向量一起使用(或者,如果'make_aggregate_objects'的参数是'std :: vector
@SergeyNikitin - 使用'std :: conditional_t'的主要优点是强制错误的参数在过程的早期导致编译错误,而不是编译错误隐藏在函数内部的某处,并引用一些内部的,无证的C++库容器的成员显然不需要做任何事情。我猜在解释C++编译错误15年之后,这对我来说价值不大。我可以免费使用其他容器重新调整模板的功能。 –
明显的答案是否定的,std::vector<T*>
和std::vector<const T*>
是两个不能交换的对象。
然而,随着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_;
};
一个更好的办法来做到这一点,国际海事组织,将内部暴露了模板化的功能和使用相反。所以,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.
}
}
你的“强制转换”方法不能编译,即使它编译了,也不会避免额外的分配,因为它按值返回(就像原来的那样)。 –
“force cast”示例应为:'template
@JonathanWakely RVO应该确保没有额外的分配完成。 – Arindam
没有办法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);
}
你的函数返回值由所以总是分配无论如何 - 什么“额外拨款”你在说什么?
如果你简单地存储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的回答所显示的那样,不需要复制函数体,也不需要用危险或危险的强制转换编写错误的代码,只需使用一个由两个函数调用的模板即可。
我编辑了原始问题以阐明我的意思是“额外”分配。此外,我还扩展了代码片段,以更好地了解数据如何存储,希望能更好地了解哪些问题正在得到解决。 –
你的函数按值返回,所以每次都要分配一个新的向量。你想避免什么“额外分配”? –
是的,函数返回的值,这意味着至少有一个堆分配必须发生(最好的情况下 - 我知道返回的矢量的大小,并可以调用'vector :: reserve')。我正在谈论的分配将发生在最简单的有关方法的实现中,不涉及拷贝粘贴:非''constst'版本调用'const'版本(它返回'std :: vector')和填充单独的'std :: vector '。创建一个单独的向量将导致至少一个('额外')分配,我想避免。在问题中澄清了这一点。 –
如果你的目标只是为了避免重复函数体,那么不要试图对任何模糊和未定义的行为做任何愚蠢的事情,只需编写一个返回'std :: vector'的函数模板,然后用'T' = ='Object'或者'T' =='const Object'。 –