保留临时的std :: string并返回c_str()以防止内存泄漏 [英] Keep temporary std::string and return c_str() to prevent memory leaks

查看:140
本文介绍了保留临时的std :: string并返回c_str()以防止内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现自己在下面使用这种类型的代码来防止内存泄漏,在性能,安全性,样式或...方面有什么问题吗?

I found myself using this type of code below to prevent memory leaks, is there anything wrong with it in terms of performance, safety, style or ...?

这个想法是,如果我需要返回一个编辑过的字符串(用c字符串而不是std :: string),我会使用一个临时的std :: string作为助手,并将其设置为我想要的返回值并保持那个暂时的生命.

The idea is that if i need to return an edited string (in terms of a c-string not std::string), i use a temporary std::string as a helper and set it to what I want my return to be and keep that temporary alive.

下次我调用该函数时,它将临时值重新设置为我想要的新值.而且由于我使用返回的c字符串的方式,所以我只读取返回的值,而不存储它.

Next time i call that function it re-sets the temporary to the new value that I want. And since the way i use the returned c-string, I only read the returned value, never store it.

另外,我应该提到,std :: string是实现细节,并且不想公开它(因此无法返回std :: string,必须返回c-string).

Also, I should mention, std::string is an implementation detail, and dont want to expose it (so cant return std::string, have to return c-string).

无论如何,这是代码:

 //in header
class SomeClass
{
private:
    std::string _rawName;

public:
    const char* Name(); // return c-string
};

//in cpp file
std::string _tempStr; // my temporary helper std::string

const char* SomeClass::Name()
{
    return (_tempStr = "My name is: " +
            _rawName + ". Your name is: " + GetOtherName()).c_str();
}

推荐答案

在C ++中,您不能简单地忽略对象生存期.您不能在忽略对象生存期的情况下与接口进行对话.

In C++ you cannot simply ignore object lifetimes. You cannot talk to an interface while ignoring object lifetimes.

如果您认为自己忽略了对象生存期,那么几乎可以肯定有一个错误.

If you think you are ignoring object lifetimes, you almost certainly have a bug.

您的接口将忽略返回缓冲区的生存期.它持续了足够长的时间"-直到有人再次打电话给我".这是模糊的保证,将导致真正的错误漏洞.

Your interface ignores the lifetime of the returned buffer. It lasts "long enough" -- "until someone calls me again". That is a vague guarantee that will lead to really bad bugs.

所有权应明确.一种明确所有权的方法是使用C风格的界面.另一种是使用C ++库类型,并要求您的客户端匹配您的库版本.另一种方法是使用自定义智能对象,并确保其在版本上的稳定性.

Ownership should be clear. One way to make ownership clear is to use a C-style interface. Another is to use a C++ library types, and require your clients to match your library version. Another is to use custom smart objects, and guarantee their stability over versions.

这些都有缺点. C风格的界面很烦人.在客户端上强制使用相同的C ++库很烦人.拥有自定义智能对象是代码重复,它会迫使您的客户端使用编写的任何字符串类,而不是他们想要使用的任何字符串类,或者编写良好的std字符串类.

These all have downsides. C-style interfaces are annoying. Forcing the same C++ library on your clients is annoying. Having custom smart objects is code duplication, and forces your clients to use whatever string classes you wrote, not whatever they want to use, or well written std ones.

最后一种方法是键入擦除,并保证类型擦除的稳定性.

A final way is to type erase, and guarantee the stability of the type erasure.

让我们看看该选项.我们键入向下擦除以分配给std之类的容器.这意味着我们忘记了要擦除的东西的类型,但是我们记得如何分配它.

Let us look at that option. We type erase down to assigning-to a std like container. This means we forget the type of the thing we erase, but we remember how to assign-to it.

namespace container_writer {
  using std::begin; using std::end;
  template<class C, class It, class...LowPriority>
  void append( C& c, It b, It e, LowPriority&&... ) {
    c.insert( end(c), b, e );
  }

  template<class C, class...LowPriority>
  void clear(C& c, LowPriority&&...) {
    c = {};
  }
  template<class T>
  struct sink {
    using append_f = void(*)(void*, T const* b, T const* e);
    using clear_f = void(*)(void*);
    void* ptr = nullptr;
    append_f append_to = nullptr;
    clear_f clear_it = nullptr;

    template<class C,
      std::enable_if_t< !std::is_same<std::decay_t<C>, sink>{}, int> =0
    >
    sink( C&& c ):
      ptr(std::addressof(c)),
      append_to([](void* ptr, T const* b, T const* e){
        auto* pc = static_cast< std::decay_t<C>* >(ptr);
        append( *pc, b, e );
      }),
      clear_it([](void* ptr){
        auto* pc = static_cast< std::decay_t<C>* >(ptr);
        clear(*pc);
      })
    {}
    sink(sink&&)=default;
    sink(sink const&)=delete;
    sink()=default;

    void set( T const* b, T const* e ) {
      clear_it(ptr);
      append_to(ptr, b, e);
    }
    explicit operator bool()const{return ptr;}
    template<class Traits>
    sink& operator=(std::basic_string<T, Traits> const& str) {
      set( str.data(), str.data()+str.size() );
      return *this;
    }
    template<class A>
    sink& operator=(std::vector<T, A> const& str) {
      set( str.data(), str.data()+str.size() );
      return *this;
    }
  };
}

现在,container_writer::sink<T>是一个相当不错的DLL安全类.它的状态是3个C样式的指针.虽然它是模板,但它也是标准布局,而标准布局基本上意味着具有与C结构相同的布局".

Now, container_writer::sink<T> is a pretty darn DLL-safe class. Its state is 3 C-style pointers. While it is a template, it is also standard layout, and standard layout basically means "has a layout like a C struct would".

包含3个指针的C结构是ABI安全的.

A C struct that contains 3 pointers is ABI safe.

您的代码使用container_writer::sink<char>,并且在DLL中,您可以为其分配std::stringstd::vector<char>. (扩展它以支持分配给它的更多方法很容易).

Your code takes a container_writer::sink<char>, and inside your DLL you can assign a std::string or a std::vector<char> to it. (extending it to support more ways to assign to it is easy).

DLL调用代码看到container_writer::sink<char>接口,并且在客户端将传递的std::string转换为该接口.这样会在客户端上创建一些函数指针 ,它们知道如何调整大小并将内容插入std::string.

The DLL-calling code sees the container_writer::sink<char> interface, and on the client side converts a passed std::string to it. This creates some function pointers on the client side that know how to resize and insert stuff into a std::string.

这些函数指针(和void*)在DLL边界上传递.在DLL方面,它们被盲目调用.

These function pointers (and a void*) pass over the DLL boundary. On the DLL side, they are blindly called.

没有分配的内存从DLL端传递到客户端,反之亦然.尽管如此,每一位数据都具有与对象相关联的明确定义的生存期(RAII样式).没有麻烦的生存期问题,因为客户端控制着要写入的缓冲区的生存期,而服务器则通过自动写入的回调对其进行写入.

No allocated memory passes from the DLL side to the client side, or vice versa. Despite that, every bit of data has well defined lifetime associated with an object (RAII style). There is no messy lifetime issues, because the client controls the lifetime of the buffer being written to, while the server writes to it with an automatically written callback.

如果您有非std样式的容器,并且想支持container_sink,这很容易.将appendclear空闲函数添加到您的类型的名称空间中,并让它们执行所需的操作. container_sink将自动找到它们并使用它们填充您的容器.

If you have a non-std style container and you want to support container_sink it is easy. Add append and clear free functions to the namespace of your type, and have them do the required action. container_sink will automatically find them and use them to fill your container.

作为示例,您可以像这样使用CStringA:

As an example, you can use CStringA like this:

void append( CStringA& str, char const* b, char const* e) {
  str += CStringA( b, e-b );
}
void clear( CStringA& str ) {
  str = CStringA{};
}

和神奇地CStringA现在对于使用container_writer::sink<char>的事物是有效的参数.

and magically CStringA is now a valid argument for something taking a container_writer::sink<char>.

使用append只是为了防万一,您需要更精细的容器构造.您可以编写一个container_writer::sink方法,该方法通过一次喂入存储容器固定大小的块来吃掉不连续的缓冲区.它会做一个清晰的,然后重复的追加.

The use of append is there just in case you need fancier construction of the container. You could write a container_writer::sink method that eats non-contiguous buffers by having it feed the stored container fixed sized chunks at a time; it does a clear, then repeated appends.

在线示例

现在,这不允许您从函数中返回值.

Now, this doesn't let you return the value from a function.

要使其正常工作,请首先执行上述操作.公开通过DLL屏障通过container_writer::sink<char>返回其字符串的函数.

To get that to work, first do the above. Expose functions that return their strings through container_writer::sink<char> over the DLL barrier.

将它们设为私人.或将它们标记为不被调用.随便.

Make them private. Or mark them as not-to-be-called. Whatever.

接下来,编写调用这些函数的inline public函数,并返回填充的std::string.这些都是纯头文件结构,因此代码位于DLL客户端中.

Next, write inline public functions that call those functions, and return the filled std::string. These are pure header file constructs, so the code lives in the DLL client.

所以我们得到:

class SomeClass
{
private:
   void Name(container_writer::container_sink<char>);
public:
   // in header file exposed from DLL:
   // (block any kind of symbol export of this!)
   std::string Name() { 
     std::string r;
     Name(r);
     return r;
   }
};

void SomeClass::Name(container_writer::container_sink<char> s) 
{
  std::string tempStr = "My name is: " +
        _rawName + ". Your name is: " + GetOtherName();
  s = tempStr;
}

完成. DLL接口起作用 C ++,但实际上只是传递了3个原始C指针.所有资源始终都拥有.

and done. The DLL interface acts C++, but is actually just passing 3 raw C pointers through. All resources are owned at all times.

这篇关于保留临时的std :: string并返回c_str()以防止内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆