想象以下类:
class MyString {
public:
const char* str;
std::size_t str_len;
MyString(const char* str, std::size_t str_len)
: str { str }
, str_len { str_len }
{}
}
我在为MyString
实施击构函数时有些困惑。我的第一个想法是看起来像这样:
~MyString() {
delete [] str;
}
但是,如果不能确定它是分配的,我该如何删除str?例如,我可以创建一个MyString
的实例:
const char* c_string = "Hello, World!";
MyString my_string(c_string, 13);
在这种情况下,i 不应该 删除 str
,因为它没有在堆上声明,但是如果我创建了这样的 MyString
实例:
char* char_array = new char[13]{'H','e','l','l','o',',',' ','W','o','r','l','d','!'};
MyString my_string(char_array, 13);
不删除str
会导致内存泄漏(我假设),因为它会在堆上声明。但是,如果我创建了一个 MyString
的实例,例如 this:
char* char_array = new char[13]{'H','e','l','l','o',',',' ','W','o','r','l','d','!'};
MyString my_string(char_array + 3, 10);
i 不应该删除 str
,因为尽管它在堆上,但尚未分配;它只是指向已分配的其他部分。
那么,我怎么能确定我不删除我不应该删除或不删除需要删除的内容的内容?如果Mystring使用char*
S而不是const char*
S,答案会有所不同吗?如果我使用MyString my_string = new MyString...
?
编辑:要澄清,我实际上并没有编写字符串类。我将字符阵列用作字节阵列。我认为STD :: String无法工作,因为字节可能为0。
有几个不同的模式适用:
-
始终分配模式。在这种方法中,班级不占有传递资源的所有权,而是在分配的缓冲区中制作副本,因此知道如何在其破坏者中进行处理。原始参数由调用类的代码所有,该呼叫者应随时清理自己的数据,因为类实例具有独立的副本。示例:
std::string
。 -
呼叫者指定的模式。在这种方法中,该类的确获得所有权,并且要适应各种分配器/Deallocator对,它接受一个参数,该参数是知道如何处理数据的函数或函数对象。类Destructor将调用此DELETER函数/功能对象,执行该特定缓冲区所需的正确Deallocation(或根本没有)。示例:
std::shared_ptr
。 -
嵌套所有权模式。在这里,该类只保留指针或对原始数据块的引用。呼叫者仍然有所有权和释放数据的责任,但是只要其创建的类实例存在,就需要将该块保持有效。这是运行时最低的开销,但也是最难跟踪的最低开销。示例:在C 11 lambda中捕获。
您用于课堂设计的哪一个,请确保您记录了它,以免班级的用户奇怪。
但是,如果不能确定它是分配的,我该如何删除
str
?
您只能在:
时删除str
您记录了您将把要传递给构造函数的指针所有权。
您记录了您将在传递给构造函数的内存上调用
delete
。仅在
new
呼叫分配的内存中构建类的实例。
我还建议更改课程以使用char*
而不是const char*
。
class MyString {
public:
char* str;
std::size_t str_len;
MyString(char* str, std::size_t str_len)
: str { str }
, str_len { str_len }
{}
}
可以防止以下意外用法:
MyString my_string("Hello, World!", 13);
然后,您必须确保遵循三个规则。
要澄清接口,您可以使用适当的智能指针,例如:
MyString(std::unique_ptr<const char[]> str, std::size_t str_len)
,或者如果您没有拥有所有权,则适当的字符串视图,例如:
MyString(std::experimental::observer_ptr<const char> str, std::size_t str_len)
那么您不再怀疑类的内存策略。