【问题标题】:Using RAII with a character pointer使用带有字符指针的 RAII
【发布时间】:2011-01-12 07:43:40
【问题描述】:

我看到很多 RAII 示例类都围绕着文件句柄。

我尝试将这些示例改编为字符指针,但运气不佳。

我正在使用的库具有获取字符指针地址的函数(声明为 get_me_a_string(char **x))。 这些函数为该字符指针分配内存,并留给库的最终用户在他们自己的代码中清理它。

所以,我的代码看起来像这样......

char* a = NULL;
char* b = NULL;
char* c = NULL;

get_me_a_string(&a);
if(a == NULL){
    return;
}


get_me_a_beer(&b);
if(b == NULL){
    if(a != NULL){
        free(a);
    }
    return;
}


get_me_something(&c);
if(c == NULL){
    if(a != NULL){
        free(a);
    }
    if(b != NULL){
        free(b);
    }
    return;
}

if(a != NULL){
    free(a);
}
if(b != NULL){
    free(b);
}
if(a != NULL){
    free(b);
}

听起来 RAII 是我上面提到的这个烂摊子的答案。 有人可以提供一个简单的 C++ 类来包装 char* 而不是 FILE* 吗?

谢谢

【问题讨论】:

  • 大多数分配内存的库都有释放它的功能。 (参见 XmStringCreate 和 XmStringFree)。你的图书馆有类似的释放功能吗?
  • 是的,它确实有自己的免费功能,但由我来调用它。

标签: c++ memory-management raii resource-management


【解决方案1】:

既然你说你不能使用 boost,那么编写一个不共享或转移资源的非常简单的智能指针并不是很难。

这里有一些基本的东西。您可以将删除器函子指定为模板参数。我不是特别喜欢转换运算符,所以请改用 get() 方法。

随意添加release()、reset()等其他方法。

#include <cstdio>
#include <cstring>
#include <cstdlib>

struct Free_er
{
    void operator()(char* p) const { free(p); }
};

template <class T, class Deleter>
class UniquePointer
{
    T* ptr;
    UniquePointer(const UniquePointer&);
    UniquePointer& operator=(const UniquePointer&);
public:
    explicit UniquePointer(T* p = 0): ptr(p) {}
    ~UniquePointer() { Deleter()(ptr); }
    T* get() const { return ptr; }
    T** address() { return &ptr; } //it is risky to give out this, but oh well...
};

void stupid_fun(char** s)
{
    *s = static_cast<char*>(std::malloc(100));
}

int main()
{
    UniquePointer<char, Free_er> my_string;
    stupid_fun(my_string.address());
    std::strcpy(my_string.get(), "Hello world");
    std::puts(my_string.get());
}

【讨论】:

    【解决方案2】:

    另一种解决方案是这样的,这就是我用 C 编写这段代码的方式:

    char* a = NULL;
    char* b = NULL;
    char* c = NULL;
    
    get_me_a_string(&a);
    if (!a) {
        goto cleanup;
    }
    
    get_me_a_beer(&b);
    if (!b) {
        goto cleanup;
    }
    
    get_me_something(&c);
    if (!c) {
        goto cleanup;
    }
    
    /* ... */
    
    cleanup:
    /* free-ing a NULL pointer will not cause any issues
     * ( see C89-4.10.3.2 or C99-7.20.3.2)
     * but you can include those checks here as well
     * if you are so inclined */
    free(a);
    free(b);
    free(c);
    

    【讨论】:

    • 在 C++ 中,这有一个问题,即由于异常,执行可能仍然永远无法达到清理。如果代码在任何地方都使用了异常,你还必须抛出一些 try 块来确保它。
    • 是的,我一直在讨论这样做(尽管通过宏)... halfbakery.com/idea/C_20exception_20handling_20macros UncleBen:这实际上只是使用 C++ 编译器的纯 C 代码。 Windows 上的 Visual Studio 和 Linux 上的 G++。
    【解决方案3】:

    感谢大家的回答。

    不幸的是,我不能在这个项目中使用 boost 或其他库......所以所有这些建议对我来说都是无用的。

    我已经看过像这里这样的 C 语言中的异常处理之类的东西...... http://www.halfbakery.com/idea/C_20exception_20handling_20macros

    然后我研究了为什么 C++ 没有像 Java 那样的 finally 并遇到了这个 RAII 的东西。

    我仍然不确定我是否会采用析构函数并仅使用 C++ 编写代码,还是坚持使用 C 异常宏(使用可怕的 goto :)

    Tronic 建议如下。 使用 RAII 或一般的析构函数,它们应该是段错误证明吗?我猜不是。

    我唯一不喜欢的是我现在必须在我的 printf 语句中使用强制转换 (char*)。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct CharWrapper {
        char* str;
        CharWrapper(): str() {}  // Initialize NULL
        ~CharWrapper() {
            printf("%d auto-freed\n", str);
            free(str);
        }
        // Conversions to be usable with C functions
        operator char*()  { return  str; }
        operator char**() { return &str; }
    };
    
    // a crappy library function that relies
    // on the caller to free the memory
    int get_a_str(char **x){
        *x = (char*)malloc(80 * sizeof(char));
        strcpy(*x, "Hello there!");
        printf("%d allocated\n", *x);
        return 0;
    }
    
    
    int main(int argc, char *argv[]){
        CharWrapper cw;
        get_a_str(cw);
        if(argc > 1 && strcmp(argv[1], "segfault") == 0){
            // lets segfault
            int *bad_ptr = NULL;
            bad_ptr[8675309] = 8675309;
        }
        printf("the string is : '%s'\n", (char*)cw);
        return 0;
    }
    

    【讨论】:

      【解决方案4】:

      标准库中已经有一些可用的东西:它叫做std::string

      编辑:根据新信息:

      它将分配内存并填充它 向上。我可以将内容复制到 新的 std::string 对象,但我仍然会 必须释放之前的内存 由函数分配。

      这对实现者来说是糟糕的设计——分配的模块应该负责解除分配。

      好的,现在我已经把它从我的系统中删除了:你可以使用boost::shared_ptr 来释放它。

      template<typename T>
      struct free_functor
      {
          void operator() (T* ptr)
          {
              free(ptr);
              ptr=NULL;            
          }
      };
      shared_ptr<X> px(&x, free_functor());
      

      【讨论】:

      • 我认为他被一个返回需要释放的 C 字符串的库所困。
      • 我认为auto_ptr 不会起作用,因为它必须是free() 而不是delete。不过,我相信boost::scoped_ptr 会让您指定自定义删除器。
      • 实际上,我猜scoped_ptr 不允许自定义删除器。但是,shared_ptr 确实如此。
      • 我从未建议过auto_ptr——如果我的帖子给人这种感觉,我宁愿编辑它。是的,shared_ptr 是我所追求的。我的错。
      • @dirkgently:你说过“......可能比它的价值更麻烦。”事实上,如果它只做delete,它就可以工作。出于同样的原因,boost::scoped_ptr 也不会。
      【解决方案5】:

      你可以试试这样的:

      template <typename T>
      class AutoDeleteArray
      {
      public:
          explicit AutoDeleteArray(const T* ptr)
              : ptr_(ptr)
          {}
          ~AutoDeleteArray()
          {
              delete [] ptr_;
              // if needed use free instead
              // free(ptr_);
          }
      
      private:
          T *ptr_;
      };
      
      // and then you can use it like:
      {
          char* a = NULL;
      
          get_me_a_string(&a);
          if(a == NULL)
            return;
      
          AutoDeleteArray<char> auto_delete_a(a);
      }
      

      这不是最可靠的解决方案,但足以达到目的。

      PS:我想知道 std::tr1::shared_ptr 是否也可以使用自定义删除器?

      【讨论】:

        【解决方案6】:

        一个非常基本的实现(您应该使其不可复制等)。

        struct CharWrapper {
            char* str;
            CharWrapper(): str() {}  // Initialize NULL
            ~CharWrapper() { free(str); }
            // Conversions to be usable with C functions
            operator char**() { return &str; }
            operator char*() { return str; }
        };
        

        这在技术上不是 RAII,因为正确的初始化发生在构造函数之后,但它会负责清理。

        【讨论】:

        • 我已经做到了。我不知道如何实际使用它。我如何声明这种类型的对象(它实际上是一个对象,你使用了结构)。如何将所述声明的对象传递给这些库函数?
        • CharWrapper str1; get_me_a_string(str1);放(str1);转换运算符可能有些问题,因此请考虑用访问器函数替换它们。 struct 和 class 之间的唯一区别是默认可见性。对于结构,它是公共的,对于类是私有的。
        • 我刚刚测试了这个。它应该能够抵抗段错误吗?如果是这样,它就不起作用,因为内存没有释放。否则它似乎工作得很好。我唯一不喜欢的是,在调用 printf 时,我现在需要将其转换为 (char*)。调用其他函数似乎完全没有任何强制转换(工作中的 C++ 重载?)
        • 如果函数采用 char* 或 char** 参数,则类型转换运算符允许它工作。由于 printf 是 vararg 函数(编译器不知道参数类型),因此无法进行自动转换。
        【解决方案7】:

        对于本地数组使用纯 std::stringboost::scoped_array,对于共享字符串使用 boost::shared_array(后者允许您提供自定义删除器以调用 free()。)

        【讨论】:

          【解决方案8】:

          我认为 auto_ptr 是你想要的

          如果 auto_ptr 语义不适合你,则提升 shared_ptr

          【讨论】:

          • auto_ptr 删除内容,但他需要free()。
          • 啊是的 - 你可以提供客户删除器但无论如何我都会投票给你的答案
          • auto_ptr 也不能很好地处理数组
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-15
          • 2021-12-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多