避免对临时对象、右值析构函数进行不必要的分配

avoiding unnecessary allocations for temporaries, r-value destructor

我有很多很多字符串,它们在创建后几乎从未修改过,对它们的访问时间并不是真正的问题。我必须尽量减少内存使用,所以我选择写一个 class 而不是使用 std::string ,它必须保存额外的数据,如容量和长度以符合标准。此更改导致内存使用量减少 ~%30。

为了实现这个,我写了一个 class 只是在内部包装 C 风格的空终止字符串并管理缓冲区所以 class 只包含一个指针数据成员,它提供了足够的很少访问和使用它的界面,例如 std::string.

我想避免样板代码并允许从 std::stringstd::string 的透明转换。那么我可以避免 allocating/copying 来自 std::string 的数据在极端情况下,例如需要临时的时候吗?考虑一下:

class A {
public:
  A(const char* p) : p_{std::strdup(p)} { }
  ~A() { free(p_); }

  A& append(const A&);

private:
  char* p_;
};

现在在下面的代码中:

A a;
a.append("test");

将创建一个临时对象,分配和复制数据。那么它将立即被销毁。问题是,有没有办法在不创建每个函数的版本的情况下避免这种情况,例如 A& append(const char*)。我也想支持std::string的透明转换,所以它会自动成为每个功能的3个版本。

下面的做法会有什么效果:

class A {
public:
  A(const char* p) : p_{std::strdup(p)} { }
  A(const char* p) &&
      : p_{p} {
  }

  ~A() { free(p_); }
  ~A() && { /* nothing */ }

  A& append(const A&);

private:
  char* p_;
};

如果这不起作用,我可以应用什么策略来解决这个问题?

What effects will the following approach have:

A(const char* p) &&
~A() &&

最有可能的影响是编译错误,因为标准不允许在 c/d-tor 上使用 ref-qualifiers。

我建议像字符串一样实现 A::c_str,并使用模板中的以下助手:

template<class Str>
const char* c_str_help(const Str& str) {
    return str.c_str();
}
const char* c_str_help(const char* str) {
    return str;
}

class A {
  // ...

  const char* c_str() const { return p_; }

  template<class Str>
  A& append(const Str& str) {
      const char* s = c_str_help(str);
      // implement using const char* s
  }
};

PS:我建议使用 unique_ptr<char, void(*)(void*)> 而不是裸指针,这样更容易实现。

有点类似于 的方法,使用 SFINAE,因此我们不会阻碍重载解析:

namespace detail_A {
    char const *getString(std::string const &str) {
        return str.c_str();
    }

    char const *getString(char const *str) {
        return str;
    }

    // More `getString`s as needed...

    template <class T>
    using enableString = decltype(getString(std::declval<T>()));
}

class A {
// ...

    template <class T, class = detail_A::enableString<T>>
    A &append(T const &str) {
        char const *p = detail_A::getString(str);

        std::cout << "Appending \"" << p << "\"\n";

        return *this;
    }
};

See it live on Coliru