我的字符串类析构函数实现



拥有这样一个简单的程序:

#include <iostream>
#include <string>
#include <windows.h>
using namespace std;
extern char MsgBuff[300];
class MyStr {
string* strPtr;
public:
// "normal" constructor
MyStr(const string& strPtr) : strPtr(new string(strPtr)) {}
// destructor
~MyStr() { 
if(strPtr != NULL)
delete strPtr; 
}
// copy constructor
MyStr(const MyStr& x) : strPtr(x.strPtr) {
OutputDebugStringA("copy constructor");
}
// move constructor
MyStr(MyStr&& x) : strPtr(x.strPtr) {
x.strPtr = nullptr;
OutputDebugStringA("copy constructor");
}
};

int main() {    
MyStr foo("Exam");
MyStr foo2 = foo;
return 0;
}

该程序抛出一个异常:Exception thrown: read access violation.正如我所推测的,这是由destructor代码引起的——破坏这两个对象(foo,foo2(,我们将释放两次strPtr指针指向的相同内存。

如何修复此代码以保留逻辑并避免异常?

此代码有一些错误。。。

MyStr(const MyStr& x) : strPtr(x.strPtr) {
OutputDebugStringA("copy constructor");
}

该代码使得";浅";类的副本,因为它只为现有对象分配地址,而不是创建新对象。这是主要的问题,因为当main((超出作用域时,将对所有初始化的对象调用析构函数。将首先调用~foo"成功地";。然后将调用~foo2,因为它仍然是一个有效的对象析构函数。

if (strPtr != NULL)

将通过,因为在您的代码中没有任何地方将strPtr设置为";nullptr";因此将调用未初始化对象上的delete。这将导致内存访问冲突。

需要记住的几件事:

  1. 请尽可能多地使用std::string。(实施此功能的人知道自己在做什么(
  2. 除非绝对必要,否则永远不要使用原始指针。(这是一种非常丑陋的做事方式,没有任何好处。请改用std::shared_ptr和std::unique_ptr。(
  3. 在调用delete后,总是将指针设置为NULL。(这是不言而喻的。不要将对象设置为无效地址。(
  4. 从不。。使用外部和/或全局变量NEVERR!!(这只是显示了糟糕的代码设计/结构(
  5. 这也不是";坏的";在";主";cpp文件,但尽量避免使用";使用命名空间std&";。在使用多个名称空间时,这将为您省去一些麻烦

现在对于";固定的";代码部分。。我想你想为字符串做一个包装器,所以这里是:

#include <iostream>
#include <memory>
#include <string>
#include <windows.h>
class MyStr {
std::shared_ptr<std::string> m_ptrStr;
public:
// "normal" constructor
MyStr(const std::string& strPtr) : m_ptrStr(std::make_shared<std::string>(strPtr)) {}
// destructor
~MyStr() { }
// shallow copy constructor (you can do this since you have "smart" pointer)
MyStr(const MyStr& x) : m_ptrStr(x.m_ptrStr) { 
OutputDebugStringA("copy constructor");
}
// move constructor
MyStr(MyStr&& x) noexcept : m_ptrStr(std::move(x.m_ptrStr)) {
OutputDebugStringA("move");
}
};

int main() {
MyStr foo("Exam");
MyStr foo2 = foo;
return 0;
}

代码中的问题在于复制ctor。

// copy constructor
MyStr(const MyStr& x) : strPtr(x.strPtr) {
OutputDebugStringA("copy constructor");
}

在这里,您不会创建字符串的浅拷贝

解决方案是执行以下操作之一:

  1. 如果内存和创建时间不是问题,请通过创建字符串并将其放入MyStr来创建深度副本
  2. 使用std::shared_ptr<std::String>而不是原始std::string*,在我看来这有点浪费
  3. 使用写时复制方法。在复制时,您不创建新的字符串,而是创建对同一资源的另一个引用,据我所知,这曾经是std::string使用的方法。(你可以在上阅读更多信息(

另一个问题是使用std::string,因为它已经完成了您想要完成的任务。如果您想使用原始指针实现自己的实现,请使用char *而不是std::string *

假设您这样做是为了实验原始指针,而不是需要容纳字符串。

当使用裸指针时,您需要正确地实现复制构造函数和赋值运算符,以便执行";深度复制";地址数据的。

每当我这样做的时候,我都会写一个";克隆";在您的情况下执行的方法:

void clone (MyStr const& src)
{
strPtr = new string(*src.strPtr);
}

此外,我建议使用";自由和零";避免双重删除的习语:

if (srcPtr)
{
delete srcPtr;
srcPtr = nullptr;
}

我推荐这本书";专业C++";由Marc Gregoire撰写,涵盖了这类细节。我拥有第三版和第四版的副本。https://www.amazon.co.uk/Professional-C-Marc-Gregoire/dp/1119421306

最新更新