使用 std::vector 时如何将索引信息传递给元素构造函数?

Posted

技术标签:

【中文标题】使用 std::vector 时如何将索引信息传递给元素构造函数?【英文标题】:How to pass index information to element constructor when using std::vector? 【发布时间】:2019-11-18 09:27:12 【问题描述】:

有什么方法可以将向量的索引传递给它的元素的构造函数? 例如:

#include <iostream>
#include <vector>

class Foo 
 public:
  Foo(unsigned long index) 
    std::cout << index << std::endl;
  
;

int main() 
  std::vector<Foo> foo;
  foo.resize(2); // any way to make this work?

此代码确实不起作用,因为编译器不知道如何构造Foo(unsigned long index),但我可以做一些技巧(例如自定义分配器?)以使此代码实际工作?

【问题讨论】:

如果没有兼容的 default-ctor,不,没有办法让 that 工作。 Foo 只能使用提供的 ctor-argument 构造。然而,我很好奇,这个解决方案试图解决的真正问题是什么,因为有可能为那个提供更合适的解决方案,而不是试图让这个工作。 @WhozCraig 我使用与此类似的代码通过向量中的“ID”(=index)查找类 Foo 对象。 @WhozCraig 我的元素是一个特殊的数据结构,恰好与索引有关系,我只是好奇我是否可以这样做,所以我问:) @reavenisadesk 我明白了。取决于Foo 来自何处的性质以及它们的管理方式(是否有不止一个foo 在各个地方浮动?Foo 可以从一个foo 移动到另一个吗?当Foofoo删除 并且此后所有后续索引不再对齐时,期望是什么?),可能值得考虑另一种身份映射。无论如何,感谢您的澄清。 【参考方案1】:

您可以在 for 循环中添加元素并将索引作为参数传递给它们的 ctor,如下所示:

// Init your vector + optionally reserve space
std::vector<Foo> foo;
const unsigned elements_to_add = 5; // or whatever number
foo.reserve(foo.size() + elements_to_add);

// foo.size() will be passed as parameter to the ctor you defined
for (std::size_t i = 0; i < elements_to_add; i++) 
    foo.emplace_back(foo.size());

【讨论】:

但是 foo.resize(2);没有给出想要的结果。 @MFnx 好吧,不必使用调整大小来填充数组。【参考方案2】:

不,您需要使用 std::generate()std::generate_n()std::back_inserter() 结合使用。

【讨论】:

可能更多std::generate_nstd::back_inserter【参考方案3】:

您可以编写一个自定义的有状态分配器,该分配器将在构造对象时传递一个索引。

小例子:

template<class T>
class Allocator 
public:
    using value_type = T;

    T* allocate(std::size_t n) 
        return static_cast<T*>(::operator new(n * sizeof(T)));
    

    void deallocate(T* p, std::size_t) noexcept 
        ::operator delete(p);
    

    template<class... Args>
    void construct(T* p, Args&&... args) 
        ::new(static_cast<void*>(p)) T(counter_++, std::forward<Args>(args)...);
    

    void destroy(T* p) noexcept 
        p->~U();
        --counter_;
    

private:
    std::size_t counter_ = 0;
;

使用示例:

struct Foo 
    Foo(std::size_t index) 
        std::cout << index << ' ';
    

    Foo(std::size_t index, const Foo& other) : Foo(other) 
        std::cout << index << ' ';
    
;

std::vector<Foo, Allocator<Foo>> foos1;
foos1.resize(3);
std::cout << std::endl;

std::vector<Foo, Allocator<Foo>> foos2;
foos2.resize(4);

// Output: 
// 0 1 2 
// 0 1 2 3

【讨论】:

但似乎我们不能保证 counter_ 是索引,尽管我还不能举出一个例子。但是你给了我一个想法,谢谢 @reavenisadesk,此解决方案依赖于调用resize() 时对象构造的特定顺序。标准只是说resize()“追加”(n - size()) 元素到序列中。 libstdc++ 做了这样的事情:for (; n &gt; 0; --n, ++cur) traits::construct(alloc, addressof(*cur));。但是 AFAIK,标准不保证这个顺序。因此,这个答案更像是一个技巧,而不是可以在实际代码中使用的解决方案。 nada 给出了一个可靠的解决方案。【参考方案4】:

我想有很多方法可以或多或少地得到你想要的。但迟早你可能会发现你不需要这个。

这是一个可能的解决方案:

#include <iostream>
#include <vector>

class Foo

    inline static unsigned long _static_index = 0;
    unsigned long _index;

public:
    Foo() : _index(_static_index)  ++_static_index; 

    auto index() const  return _index; 
    static void resetIndex()  _static_index = 0; 
;

int main()

    std::vector<Foo> foos;
    Foo::resetIndex();
    foos.resize(2);

    for (const auto& f : foos)
        std::cout << f.index() << std::endl;

    return 0;

因此,您只需增加一个静态计数器并将其分配给私有成员 _index。这显然有其局限性。例如,假设您在填写 Foos 的 vector 之前创建了 3 个 Foo 实例,那么 foos[0].index() 将返回 3 而不是 0。因此,在填写 foos 之前,您需要重置 @ 987654328@。

【讨论】:

如果要放入 Foos 的数组不止 1 个,这将行不通。 另外不要忘记内联_static_index,否则你必须在其他地方初始化它。并且以 _ 开头的变量名称不是很 C-plus-plussy。 @nada 我在 C++ 中看到了很多 _someMember。这当然不是“不是很 C+plussy”。 @nada 不,这行不通。明显的限制......但是,根据问题, foos.resize(2) 应该可以工作。 @nada 1. 下划线 + 小写字母对于全局命名空间之外的标识符(即类成员)是可以的,所以这是 100% 合法的,2. 这不是我的任何选择我见过的地方。

以上是关于使用 std::vector 时如何将索引信息传递给元素构造函数?的主要内容,如果未能解决你的问题,请参考以下文章

为 std::vector 键入安全索引值

如何将智能指针传递给函数?

将数据从 std::vector 传递到 std::valarray 的最有效方法

通过引用传递的 std::vector 不是从函数传递到 main()

如何确定`range :: view`对象和`std :: vector`之间的等价?

Swig:将 std::vector<unsigned char> 传递给从 c++ 生成的 c# 函数