使用带有字符指针的 RAII

Posted

技术标签:

【中文标题】使用带有字符指针的 RAII【英文标题】:Using RAII with a character pointer 【发布时间】: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)。你的图书馆有类似的释放功能吗? 是的,它确实有自己的免费功能,但由我来调用它。 【参考方案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::string 或 boost::scoped_array,对于共享字符串使用 boost::shared_array(后者允许您提供自定义删除器以调用 free()。)

【讨论】:

【参考方案8】:

我认为 auto_ptr 是你想要的

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

【讨论】:

auto_ptr 删除内容,但他需要free()。 啊是的 - 你可以提供客户删除器但无论如何我都会投票给你的答案 auto_ptr 也不能很好地处理数组

以上是关于使用带有字符指针的 RAII的主要内容,如果未能解决你的问题,请参考以下文章

RAII&智能指针

RAII&智能指针

使用带有字符串指针的 isalpha 函数

RAII和模拟实现智能指针

使用智能指针来管理对象 (基于RAII)

RAII思想之智能指针