用 C++ 编写 2D 地图的三法则

Writing the Rule of Three for a 2D map in C++

提问人:user83975 提问时间:3/6/2023 更新时间:3/6/2023 访问量:94

问:

我正在制作一个程序来确定一条推文是快乐还是悲伤,我想我把推文标记化,然后创建一个地图,将这个词存储为键,它总共使用了多少次,以及它在快乐的推文和悲伤的推文中使用了多少次。

我认为这是一个很好的方法,虽然可能有更好的选择,但我不太明白如何为我想创建的地图编写三法则。Classifier 对象将是用于对推文的情绪进行分类的对象,下面是 CLassifier.h 文件,不包括用于训练数据的其他方法(DSString 对象是一个字符数组)

class Classifier {
private:
  map<DSString, map<char, float>> *words;

public:
  Classifier();
  
  Classifier(const DSString& objToCopy);  // Copy Constructor
  ~Classifier();  // Destructor
  Classifier &operator=(const Classifier& objToCopy);  // Copy Assignment Overload
}

下面是 CLassifier .cpp 文件,其中不包含用于训练和预测的方法的代码。

using namespace std;

Classifier::Classifier() {
  map<DSString, map<char, float>> *words;
}

// 1/3 Copy Constructor
Classifier::Classifier(const Classifier& objToCopy) {
  words = new map<DSString, map<char, float>>(*objToCopy.words);
}

// 2/3 Destructor
Classifier::~Classifier() {
  delete words;
  words = nullptr;
}

// 3/3 Copy Assignment Operator Overload
Classifier& Classifier::operator=(const Classifier& objToCopy) {
  if (this != &objToCopy) {
    delete words;
    words = new map<DSString, map<char, float>>(*objToCopy.words);
  }
  return *this;
}

当我尝试编译程序时,我收到一个关于我的复制构造函数的错误,内容如下:

Classifier.cpp:15:51: error: definition of implicitly-declared ‘constexpr Classifier::Classifier(const Classifier&)’
   15 | Classifier::Classifier(const Classifier& objToCopy) {

我做错了什么,我无法编译我的程序?如果我错误地使用地图,我可以更改哪些内容以使程序仍然正常运行?有没有更好/更有效的方法来做到这一点?我需要扫描大约 20k 条推文的 CSV。

C++ 构造函数 深度复制 三法则

评论

4赞 john 3/6/2023
删除指针,你没有任何问题,为什么指针在那里?指向标准库容器的指针很少是一个好主意。标准容器背后的想法是避免使用显式指针和动态内存管理。
0赞 Some programmer dude 3/6/2023
除了@john提到的问题之外,默认构造函数不会以任何方式初始化指针。它所做的只是在构造函数内部本地定义一个具有相同名称的全新变量。Classifier
0赞 user83975 3/6/2023
我想对于存储大量数据的对象,您想在堆上使用内存吗?
2赞 Some programmer dude 3/6/2023
“我想,对于存储大量数据的对象,你想在堆上使用内存吗?”映射本身不会将其所有数据存储在对象中,它将使用指针和堆分配的数据作为其树。
3赞 john 3/6/2023
@McLeanTurner 是的,但这就是 map 的工作方式,它在内部将所有内存分配给堆上。地图本身不需要位于堆上。(这与向量、字符串或任何其他容器类没有什么不同)。

答:

3赞 Jim Rhodes 3/6/2023 #1

头文件中有错误。您在复制构造函数中使用了错误的类名。你有这个:

Classifier(const DSString& objToCopy);  // Copy Constructor

应该是这样的:

Classifier(const Classifier& objToCopy);  // Copy Constructor

评论

0赞 user83975 3/6/2023
我觉得自己太傻了,哈哈。谢谢,我认为这解决了哈哈
0赞 Jim Rhodes 3/6/2023
但您可能也要听从评论和其他答案的建议。
3赞 john 3/6/2023 #2

这是在没有指针的情况下重写的代码(并修复了错误)

class Classifier {
private:
  map<DSString, map<char, float>> words;

public:
};

这称为零规则,因为您不需要编写析构函数、复制构造函数或复制赋值运算符。编译器生成的是正确的。

0赞 Remy Lebeau 3/6/2023 #3

复制构造函数声明不正确。它接受一个对象作为输入,但它需要接受一个对象。DSStringClassifier

此外,默认构造函数根本不初始化成员,这会导致代码的其余部分在尝试对成员执行操作时具有未定义的行为words

此外,您的复制赋值运算符不需要分配一个全新的,它应该只是将数据从源复制到现有的。自己的复制分配运算符将释放旧数据并为您复制源数据。std::mapstd::mapstd::mapstd::map

正确的 Rule-of-3 实现看起来更像是这样:

#include <map>

class Classifier {
private:
  using WordMap = std::map<DSString, std::map<char, float>>;
  WordMap *words;

public:
  Classifier();
  
  Classifier(const Classifier& objToCopy);  // Copy Constructor
  ~Classifier();  // Destructor
  Classifier &operator=(const Classifier& objToCopy);  // Copy Assignment Overload
};
#include "Classifier.h"

Classifier::Classifier() {
  words = new WordMap();
}

Classifier::Classifier(const Classifier& objToCopy) {
  words = new WordMap(*objToCopy.words);
}

Classifier::~Classifier() {
  delete words;
}

Classifier& Classifier::operator=(const Classifier& objToCopy) {
  if (this != &objToCopy) {
    *words = *objToCopy.words;
  }
  return *this;
}

在 C++11 及更高版本中,您还应该通过添加移动构造函数和移动赋值运算符来实现 5 规则,例如:

#include <map>

class Classifier {
private:
  using WordMap = std::map<DSString, std::map<char, float>>;
  WordMap *words;

public:
  Classifier();
  
  Classifier(const Classifier& objToCopy);  // Copy Constructor
  Classifier(Classifier&& objToMove);  // Move Constructor
  ~Classifier();  // Destructor
  Classifier &operator=(const Classifier& objToCopy);  // Copy Assignment Overload
  Classifier &operator=(Classifier&& objToMove);  // Move Assignment Overload
};
#include "Classifier.h"
#include <utility>

Classifier::Classifier() {
  words = new WordMap();
}

Classifier::Classifier(const Classifier& objToCopy) {
  words = new WordMap(*objToCopy.words);
}

Classifier::Classifier(Classifier&& objToMove) {
  words = new WordMap(std::move(*objToMove.words));
}

Classifier::~Classifier() {
  delete words;
}

Classifier& Classifier::operator=(const Classifier& objToCopy) {
  if (this != &objToCopy) {
    *words = *objToCopy.words;
  }
  return *this;
}

Classifier& Classifier::operator=(Classifier&& objToMove) {
  *words = std::move(*objToMove.words);
  return *this;
}

或者:

#include <map>

class Classifier {
private:
  using WordMap = std::map<DSString, std::map<char, float>>;
  WordMap *words;

public:
  Classifier();
  
  Classifier(const Classifier& objToCopy);  // Copy Constructor
  Classifier(Classifier&& objToMove);  // Move Constructor
  ~Classifier();  // Destructor
  Classifier &operator=(const Classifier& objToCopy);  // Copy Assignment Overload
  Classifier &operator=(Classifier&& objToMove);  // Move Assignment Overload

  void swap(Classifier& objToSwap);
};
#include "Classifier.h"
#include <utility>

Classifier::Classifier() {
  words = nullptr;
}

Classifier::Classifier(const Classifier& objToCopy) : Classifier() {
  if (objToCopy.words)
    words = new WordMap(*objToCopy.words);
}

Classifier::Classifier(Classifier&& objToMove) : Classifier() {
  objToMove.swap(*this);
}

Classifier::~Classifier() {
  delete words;
}

Classifier& Classifier::operator=(const Classifier& objToCopy) {
  if (this != &objToCopy) {
    Classifier(objToCopy).swap(*this);
  }
  return *this;
}

Classifier& Classifier::operator=(Classifier&& objToMove) {
  Classifier(std::move(objToCopy)).swap(*this);
  return *this;
}

void Classifier::swap(Classifier& objToSwap) {
  std::swap(words, objToSwap.words);
}

话虽如此,由于已经为自己的数据实现了 Rule-of-3/5,因此您根本不需要动态使用它。您应该努力实现 Rule-of-0,只需完全删除指针并让编译器生成的构造函数/运算符为您调用对象的相应方法,例如:std::mapstd::map

#include <map>

class Classifier {
private:
  std::map<DSString, std::map<char, float>> words;

public:
  // everything you need is auto-generated for you!
  // a generated default constructor will default-construct the map...
  // a generated copy constructor will copy the map...
  // a generated move constructor will move the map...
  // a generated destructor will destroy the map...
  // a generated copy assignment will copy the map...
  // a generated move assignment will move the map...
};

看看这有多简单?:-)