有没有办法将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()更好地表明,这些方法的实现是比较复杂的,然后基于范围的循环 - 因此避免复制的愿望 - 实施。

+0

你的函数按值返回,所以每次都要分配一个新的向量。你想避免什么“额外分配”? –

+0

是的,函数返回的值,这意味着至少有一个堆分配必须发生(最好的情况下 - 我知道返回的矢量的大小,并可以调用'vector :: reserve')。我正在谈论的分配将发生在最简单的有关方法的实现中,不涉及拷贝粘贴:非''constst'版本调用'const'版本(它返回'std :: vector ')和填充单独的'std :: vector '。创建一个单独的向量将导致至少一个('额外')分配,我想避免。在问题中澄清了这一点。 –

+1

如果你的目标只是为了避免重复函数体,那么不要试图对任何模糊和未定义的行为做任何愚蠢的事情,只需编写一个返回'std :: vector '的函数模板,然后用'T' = ='Object'或者'T' =='const Object'。 –

简短的回答是:不。 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的一些帮助,私有类方法可以变成普通方法,而不是模板方法。

+0

这似乎是最简单的解决方案最完整的答案。谢谢!只是想补充一点:尽管你把这种方法称为通用,但我想要指出的是,如果我遇到这样的代码,我可能会为了“v_type”被模板化而思考我的头脑。这是否打算与不同的向量一起使用(或者,如果'make_aggregate_objects'的参数是'std :: vector &v'),则完全不同的实体?我认为** tmlen **提出的'std :: conditional_t'方法增加了这种误解的清晰度,也许有些人会发现它更有用。 –

+0

@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. 
    } 
} 
+2

你的“强制转换”方法不能编译,即使它编译了,也不会避免额外的分配,因为它按值返回(就像原来的那样)。 –

+0

“force cast”示例应为:'template std :: vector &remove_const_from_result(std :: vector &input){return reinterpret_cast &>(input); }' – Quuxplusone

+0

@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可以是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); 
} 

你的函数返回值由所以总是分配无论如何 - 什么“额外拨款”你在说什么?

如果你简单地存储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的回答所显示的那样,不需要复制函数体,也不需要用危险或危险的强制转换编写错误的代码,只需使用一个由两个函数调用的模板即可。

+0

我编辑了原始问题以阐明我的意思是“额外”分配。此外,我还扩展了代码片段,以更好地了解数据如何存储,希望能更好地了解哪些问题正在得到解决。 –