避免在没有原始指针的情况下复制地图的密钥

时间:2011-02-06 21:13:39

标签: c++ c++11 smart-pointers stdstring stdmap

每次在std :: map中插入一对,其键是std :: string时,它会生成两个副本。您可以避免使用原始指针,但它是异常不安全的。有没有办法使用智能指针而不是原始指针?

示例代码:

// To compile: g++ -std=c++0x exmaple.cpp -o example 

#include <iostream>
#include <string>
#include <map>
#include <memory>

class StringSquealer: public std::string
{
  public:
    StringSquealer(const std::string s) : std::string(s) {}
    StringSquealer(const StringSquealer&) 
    { 
      std::cout << "COPY-CONSTRUCTOR" << std::endl; 
    }
};

int main()
{
  // Inefficient
  std::map<StringSquealer,int> m1;
  m1[StringSquealer("key")] = 1;
  std::cout << "---" << std::endl;

  // Exception-unsafe
  std::map<StringSquealer*,int> m2;
  m2[new StringSquealer("key")] = 1;

  //Ideal??
  std::map<std::unique_ptr<StringSquealer>,int> m3;
  std::unique_ptr<StringSquealer> s(new StringSquealer("key"));
  //!m3[std::move(s)] = 1;  // No compile
}

输出:

COPY-CONSTRUCTOR
COPY-CONSTRUCTOR
---

3 个答案:

答案 0 :(得分:7)

这是低效的,因为你写错了你的课。 C ++ 0x提供了右值引用 - 你只是编写了你的​​类,因此它无法利用它们。

class StringSquealer: public std::string
{
  public:
    StringSquealer(std::string&& s) : std::string(std::move(s)) {}
    StringSquealer(const std::string& s) : std::string(s) {}
    StringSquealer(const StringSquealer& s)
        : std::string(s) 
    { 
      std::cout << "COPY-CONSTRUCTOR" << std::endl; 
    }
    StringSquealer(StringSquealer&& s)
        : std::string(std::move(s)) 
    {
        std::cout << "MOVE-CONSTRUCTOR" << std::endl;
    }
};

unique_ptr作为关键?这不可能。即使你以某种方式获得相同的指针并从中构造了一个unique_ptr,你也永远无法找回相同的unique_ptr - ,一旦比较你就会删除密钥已经完成了。

答案 1 :(得分:2)

在进一步详细说明之前,请确保不要进行任何类似的优化,因为无论您确定制作副本的成本如此之大,以至于您需要解决它。将字符串作为键是很好和直观的,避免它的代码有点毛茸茸。

使用unique_ptr作为地图中的键确实可以工作,但我真的认为这不是一个好主意。这意味着,为了查询映射中的键,您必须将该字符串用作存储为unique_ptr的键。这意味着除非您将所有字符串存储为unique_ptrs,否则您需要复制每个要查找的字符串。由于插入往往不如查找常见,因此这似乎以牺牲常见情况为代价来优化不常见的情况。我强烈反对你这样做。

如果您确实想要摆脱不必要的复制,您可能需要考虑选择执行写入时复制的字符串的实现。这样,制作字符串副本的成本是O(1),并且在插入期间制作的两个副本将是便宜的。这可能需要您在其他地方使用此字符串实现,并且必须小心多线程问题,但如果您愿意,可以使其工作。

答案 2 :(得分:1)

这里有些问题:

  • 您不应该从std :: string
  • 派生类
  • 您不应将unique_ptr用作地图中的键

您可以使用shared_ptr作为键,然后您需要一个比较类来比较共享指针。

然而,你最好只使用std :: string作为键,除非它们是非常长的字符串,因此复制它们很昂贵。

顺便说一句,复制中最昂贵的部分可能是分配而不是复制本身。为此,您可以考虑将basic_string与自定义分配器一起使用。