【问题标题】:STL string member in class contained in std::vectorstd::vector 中包含的类中的 STL 字符串成员
【发布时间】:2013-10-31 10:42:57
【问题描述】:

我有一个以 stl std::string 为成员的 c++ 类 Foo。此外,我有一个 STL std::vector<Foo> vecFoo 按值包含类 Foo 的对象(不是指向 Foo 对象的指针)。现在有朋友建议我不要以这种方式实现它,而是用指针构建向量:std::vector<Foo*> 或使用 boost 智能指针。他讨论了当包含的类具有像std::string 这样的动态成员时,涉及对其成员的大量复制操作(在添加成员时分配更多空间等)的向量会产生问题。会不会出现什么问题?

据我了解,std::string 实际上会在类 Foo 可能被 std::vector 复制时进行深度复制(或写入时复制),因为 Foo 为其所有成员调用复制构造函数。我的朋友争辩说,当向量分配新空间时,如果向量中所有 Foo 对象中的字符串成员的长度不同,这是一个问题。你怎么看?

在向量内使用指向Foo 的指针的唯一原因是速度。指向 Foo (Foo*) 的指针的复制速度比完整的类 Foo 快得多,不是吗? 谢谢讨论!

【问题讨论】:

  • afaik,std::vector 将它的“值”存储在堆上,因此使用指针对我来说毫无意义
  • 这个问题有点太开放了,可能会被关闭。但是,我建议您坚持当前的方法,直到您证明这是一个真正的问题(移动语义意味着它可能永远不会出现)。
  • 你有 C++11 吗?如果是这样,请给您的班级nothrow 移动复制构造函数和移动赋值运算符。这将使所有内部移动变得便宜。但总的来说,先剖析再看。
  • @juanchopanza 或者更好,不要,让编译器来做。
  • @BoBTFish 同意,但这是假设该类型没有禁用默认的移动复制和分配特殊功能。

标签: c++ string vector stl


【解决方案1】:

我猜,如果您要使用new 分配每个元素对象,带有指针的std::vector 根本不会更有效。但是如果你的元素对象足够大(不是因为字符串长度,而是因为它的“非指针”成员)带有指针的向量可以执行更好的或不 - 这取决于向量重新分配的频率。

考虑std::vector 分配空间的事实对数很少,因为它分配的空间总是比之前分配的空间多两倍。

C++11 的情况会更好,std::vector 将使用move semantics 来避免复制字符串的字符。但是,如果您的编译器不生成 default move constructor,您可能需要在您的类中实现 move constructor 才能使这个东西起作用。

甚至在 C++11 之前,一些 std::string 实现就使用了写时复制策略,因此只需对字符串进行“深度复制”就不需要复制底层字符数组,除非其中一个副本被修改。

如果字符串成员之间的长度不同,则会出现问题 向量中的所有 Foo 对象

这肯定不是问题——std::string 对象当然在内部保存了一个指向字符数组的指针,而不是数组本身。

我相信,您应该在做出此类级别的优化决策之前使用分析器分析您的程序

【讨论】:

  • 我不确定“大多数 std::string 实现”是否使用 COW。无论如何,这已在 C++11 中删除。
  • 谢谢回答。可以使用哪些工具来衡量 Visual Studio 2012 中的性能?有没有集成?
  • @Michbeckable MS VS 2012 在其专业版、高级版和终极版中有 built in profiler。请参阅here 使用其他 VS 版本时如何配置文件。
【解决方案2】:

这里有一个权衡。您的 Foo 是否足够复杂以至于复制它比复制指针或智能指针要昂贵得多?您是否希望向量在执行期间经常调整大小,从而导致复制?或者,与使用指针(并且必须在之后清理)或智能指针的额外复杂性相比,复制 Foo 的向量的成本是否可以接受?在向量中存储指针有其自身的成本,因为指向的对象在内存中不一定是连续的。 (按值存储时,Foos 中包含的字符串会有数据分散在内存中。)

尝试简单的实现(Foo的向量),测量它的性能,然后才考虑切换到(智能)指针的优化。

【讨论】:

  • 谢谢,你的权衡是正确的。我的课没有那么复杂,它只有一些普通的数据成员 float、int 等。而且向量永远不会超过 20-30 个项目。所以我选择了按值实现。
【解决方案3】:

你有两个选择:

  1. 使用 std::unique_ptr 或 std::shared_ptr
  2. 按值使用并提供移动语义

如果你使用第二个选项你必须以这种方式定义Foo

struct Foo
{
    // default ctor
    Foo( const std::string& s = "" ) : name( s ) {}

    // copy ctor
    Foo( const Foo& rhs ) : name( rhs.name ) {}

    // move ctor
    Foo( Foo&& rhs ) : name( std::move( rhs.name ) ) {}

    // assign operator
    Foo& operator = ( const Foo& f )
    {
        name = f.name;
        return *this;
    }

    // move operator
    Foo& operator = ( Foo&& f )
    {
        std::swap( name, f.name );
        return *this;
    }

    string name;
};

FooVec v;

// construct in place
v.emplace_back( "foo1" );
v.emplace_back( "foo2" );

// or move
v.push_back( std::move( Foo( "foo3" ) );

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-23
    • 2022-01-03
    • 1970-01-01
    • 1970-01-01
    • 2014-02-18
    • 2011-01-14
    • 2021-10-31
    相关资源
    最近更新 更多