【问题标题】:How to implement copy operator for such C++ structure?如何为这种 C++ 结构实现复制运算符?
【发布时间】:2011-06-18 09:59:06
【问题描述】:

所以有

struct ResultStructure
{
    ResultStructure(const ResultStructure& other)
    {
        // copy code in here ? using memcpy ? how???  
    }
    ResultStructure& operator=(const ResultStructure& other)
    {
        if (this != &other) {
            // copy code in here ?
        }
        return *this
    }
    int length;
    char* ptr;
};

如何实现“复制构造函数”和“赋值运算符”? (对不起 - 我是 C++ nube)

更新:sbi 和其他人问 - 为什么我要手动处理原始内存?我的答案很简单——在我参与的一个学生项目中,现在我们使用了很多 C 库,例如 OpenCV OpenAL 和 FFmpeg,还有更多。目前使用 C++,我们尝试创建一个基于图形的直接显示,如跨平台库,这将有助于实时视频广播和处理。我们的图形元素目前使用 char* 和 int 对进行数据交换。要将数据转换为订阅的元素,我们现在使用原始 memcpy。我想更进一步,让我们能够将我们的图形元素作为 C++ 模板的基础。因此,一个图形元素将能够与其他图形元素共享当前的图形元素数据,并且它共享的数据将是一种结构,其中不包含一个 char* 一个 int,而是包含任意数量的数据字段和几乎任何内部元素。所以这就是为什么我需要了解如何创建一个实现“复制构造函数”和“赋值运算符”的基本 C++ 结构,以便我能够为我们使用新的数据转换算法,如

void CastData(T item){
    for(size_t i = 0 ; i < FuncVec.size(); i++){
        T dataCopy = item;
        FuncVec[i](dataCopy);
    }
}

而不是当前使用的

void CastData(char * data, int length){
    for(size_t i = 0 ; i < FuncVec.size(); i++){
        char* dataCopy = new char[length];
        memcpy(dataCopy, data, length);
        FuncVec[i](dataCopy, length);
                    delete[] dataCopy;
    }
}

【问题讨论】:

  • 术语更正:有“拷贝构造函数”和“赋值运算符”。没有“复制操作员”。不过,这个名称并没有什么问题,只是没有使用它,可能是为了避免与复制构造函数混淆。

标签: c++ oop class copy operators


【解决方案1】:

您可能想解释为什么您要手动处理原始内存。我已经很久没有这样做了,这就是 std::stringstd::vector 的设计目的:

struct ResultStructure
{
    // nothing else needed

    std::string data; // or std::vector<char>
};

但是,如果您真的需要以艰难的方式来做这件事(这是家庭作业吗?),那么请注意,一开始要做到这一点非常困难。例如,赋值运算符的简单实现可能是这样的:

// DON'T TRY THIS AT HOME!!
ResultStructure& ResultStructure::operator=(const ResultStructure& rhs)
{
  delete[] ptr;                               // free old ressource
  ptr = new char[rhs.length];                 // allocate new resourse
  std::copy(rhs.ptr, rhs.ptr+rhs.length, ptr; // copy data
  length = rhs.length;
}

如果有人不小心将一个对象分配给自己(如果您只有两个引用并且您不怀疑它们引用同一个对象,则可能会发生这种情况),那么这将致命地失败。
另外,如果new 抛出异常怎么办? (如果内存耗尽,它可能会抛出std::bad_alloc。)那么我们已经删除了旧数据并且没有分配新数据。然而,指针仍然指向旧数据曾经所在的位置(实际上,我认为这是实现定义的,但我还没有看到在删除时更改 ptr 的实现)和类的析构函数(你 知道该类需要一个析构函数,对吗?)然后会尝试删除没有分配数据的地址处的一条数据。那是未定义的行为。您可以期望的最好的结果是它会立即崩溃。

最简单的方法是使用Copy-And-Swap idiom

struct ResultStructure
{
    ResultStructure(const ResultStructure& other)
     : ptr(new char[rhs.length]), length(rhs.length)
    {
        std::copy(rhs.ptr, rhs.ptr+rhs.length, ptr);
    }

    ~ResultStructure() // your class needs this
    {
      delete[] ptr;
    }

    ResultStructure& operator=(ResultStructure rhs) // note: passed by copy
    {
        this->swap(rhs);
        return *this
    }

    void swap(const ResultStruct& rhs)
    {
      using std::swap;
      swap(length, rhs.length); 
      swap(ptr, rhs.ptr); 
    }

    std::size_t length;
    char* ptr;
};

请注意,我添加了一个析构函数,更改了赋值运算符以传递每个副本的参数(我们需要调用复制构造函数来分配内存),并添加了一个swap 成员函数。按照惯例,swap() 函数永远不会抛出并且速度很快,最好是 O(1)。

我知道GMan's discussion of the Copy-And-Swap idiom 已经足够详尽和令人筋疲力尽,而mine 对你来说可能太简洁了,一开始你可能不会理解很多,但试着坚持下去,尽可能多地理解可能的。

【讨论】:

  • 为什么不允许我们多次投票?该死!一个很好的解决方案,有一个干净的好实现。干得好。
  • @David:因为那会导致投票战?记住,我比你有更多的代表参加这样的战斗! :)
  • 更新了你的问题的答案=)
【解决方案2】:

如果您使用std::string,而不是char*,您甚至不需要编写operator= 或复制构造函数。编译器生成的代码会很好地完成您的工作。

但作为一般解决方案(对于其他一些场景),请使用复制和交换习语:

Exceptional C++ by Herb Sutter 详细描述了这些。我建议您阅读本书中的内容。暂时可以在线阅读这篇文章:

【讨论】:

  • 恐怕有人提出这样的问题,Sutter 的Exceptional C++,虽然是一本非常好的书,但可能仍然,嗯,有点遥不可及。我宁愿建议先从 Meyers 的 Effective C++ 开始。 newest edition 在第 11 项“处理operator= 中的自我分配”中对此进行了很好的处理。
【解决方案3】:

简单的解决方案是使用std::string 而不是char* 成员。

然后编译器生成的复制构造函数和复制赋值运算符就可以工作了。

作为一项规则,尤其是作为新手,不要有原始指针成员。

干杯,

【讨论】:

  • +1 作为一项规则,特别是作为新手,不要使用原始指针成员。 这是您在开始时可以获得的最佳建议之一语言。如果可能的话,我会投票两次。
【解决方案4】:

正如已经说过的,并且正如在这个问题中所建议的那样,您可能应该重用现有的容器。至少代码是正确的:)

出于教育目的,让我们检查一下这个结构:

class Result
{
public:

private:
  size_t length; // can't really be negative, right ?
  char* data;
};

在这里,我们需要显式的内存管理。这意味着,值得注意的是,遵循Rule Of Three(感谢 Fred)

让我们从实际构建我们的对象开始:

Result::Result(): length(0), data(0) {}

Result::Result(size_t l, char* d): length(0), data(0)
{
  if (!d) { return; }

  data = new char[l]; // this may throw, so we do it first

  length = l;
  memcpy(data, d, l);
}

现在我们可以实现传统的运算符了:

// Swap
void Result::swap(Result& other)
{
  using std::swap;

  swap(length, other.length);
  swap(data, other.data);
}

// Copy Constructor
Result::Result(Result const& other): length(0), data(0)
{
  if (!other.length) { return; }

  data = new char[other.length];
  length = other.length;
  mempcy(data, other.data, length);
}

// Assignemt Operator
Result& Result::operator=(Result other)
{
  this->swap(other);
  return *this;
}

// !IMPORTANT!
// Destructor
Result::~Result()
{
  delete[] data; // use the array form of delete
                 // no need to test for nullity, it's handled
}

【讨论】:

  • 这是std::size_t:) 无论如何,+1 来自我,因为这是正确的(尽管它大部分与我的重复)。
  • @sbi: 没有看到你的解决方案 :) 我已经切换到课堂,如果你会说的话,因为这里有一个必需的不变量,struct 更难维护。
  • 是的,这(以及其他一些细节)是我写“大部分”的原因。如果你在chat,你就会知道我的回复。 :)我有什么办法引诱你?
  • 哪些函数应该是私有的,哪些是公共的?
  • @Kabumbus:通常情况下,它们都是公开的。也就是说,您可能希望一个类型不是可构造/可破坏的,而不是由其子级(基类的有用属性)等......但这是一个极端情况。
【解决方案5】:

这是std::vector&lt;char&gt; 的工作 - 或者这是家庭作业?

向量将替换lengthptr 后面的分配。向量是 c++ 习惯用法,如果您像您描述的那样实现类,您将不会与其他 c++ 开发人员交朋友。当然,也有极端情况,但标准容器如vector是默认的。

vector 知道如何复制 chars 以及它自己,并且实现已经过优化和测试。

以下是使用向量显式实现复制 ctor/assign 的方法:

struct ResultStructure {
    ResultStructure(const ResultStructure& other) : d_chars(other.d_chars) {
    }

    ResultStructure& operator=(const ResultStructure& other) {
        if (this != &other) {
            this->d_chars = other.d_chars;
        }
        return *this;
    }
    std::vector<char> d_chars;
};

【讨论】:

  • std::string 在这里可能会更好。此外,更喜欢复制和交换习语而不是if (this != &amp;other) 技巧。
  • @Matthieu M. 也许std::string 会更好。由于 OP 没有指定 length+ptr 是表示数据还是字符串,所以我假设数据。关于保留 OP 的 if (this != &amp;other):我只实现了 OP 的要求。
  • @Justin:我知道这是 OP 所要求的(因此没有反对意见),但我更愿意尽可能教育人们使用更好的习语:) 复制和交换重用复制构造函数的代码,因此赞成消除冗余(虽然老实说,我的口味仍然太多...... C ++很冗长:/)
  • @Justin:一旦你使用了std::vector,复制构造函数和赋值运算符就都不需要了。编译器生成的实现就足够了。
  • @sbi 是的,i 知道 std::vector 实现了这一点并且编译器正确地生成了这些 - 我只是在回答这个问题而没有过于详细(有确实可以涵盖更多细节)。无论如何,+1 因为这点可能会帮助不知道的人。
【解决方案6】:

我认为这应该可以完成工作:

struct ResultStructure
{
    ResultStructure(const ResultStructure& other);
    ResultStructure& operator=(const ResultStructure& other);

    int length;
    char* ptr;
};

ResultStructure::ResultStructure(const ResultStructure& other):length(other.length)
{
    ptr = (char*)malloc(length);
    memcpy(ptr, other.ptr, length);
}

ResultStructure& ResultStructure::operator=(const ResultStructure& other)
{
    length = other.length;
    ptr = (char*)malloc(length);
    memcpy(ptr, other.ptr, length);
    return *this;
}

请记住在析构函数中释放 ptr。 ptr下存储了什么?如果是文本,为什么不使用 std::string?无论如何,您可以使用 std::vector。那么构造函数会容易得多......

【讨论】:

  • 您没有使用初始化列表(不好的风格),提倡在 C++ 中使用 malloc()(错误代码),并且您的赋值运算符不会释放旧内存(错误代码)。这是我非常确定的-1
  • @Kabumbus:如果你对 C++ 的了解不够好,无法接受一个好的答案(或者至少没有像这个那样致命错误的答案),你可能想要使用投票作为一个方向。纳瓦兹的回答非常好。如果您不明白,请询问
  • @sbi Nawaz 的答案总是很好,但通常没有代码示例。
  • @Kabumbus:我明白了。我已经发布了一个答案,希望它的详细程度足以让您了解所涉及的一些问题。
  • @Kabumbus 请注意,您的“正确编译”的实现没有解决 sbi 指出的任何问题。 fwiw,sbi 提出的观点都值得我的代码库拒绝,因为它们不符合他们的标准。事实上,还有其他一些问题 - 最突出的是使用 c 样式转换。
【解决方案7】:

ptr 指向的内存是如何分配的?

如果使用new,分配new,设置length然后复制

other.length = length;
other.ptr = new char[length];
memcpy( other.ptr, ptr, length );

如果您使用malloc 分配内存,请用对malloc 的调用代替对new 的调用。如果您正在使用其他一些内存方案,请弄清楚。 :)

【讨论】:

  • -1 代码不正确(因为我正在写这个)。此外,在提倡new 和原始指针时,至少应该讨论三原则。
  • @Alf P. Steinbach:仔细阅读,我不提倡什么。只是试图让他理解他的问题有一个主观领域(内存分配),而不会用每一个可能的迂腐论点来混淆一切。如果是一个大词。
  • @Kabumbus:这是一个指针。在您链接的 codepad.org 代码中,您使用 . 访问本地类成员。使用指针表示法,即-&gt;,如this-&gt;lengththis-&gt;ptr。正如 Alf 指出的,这不是完美的代码。我是在给你鱼而不是试图教你如何钓鱼。在这个网站上,我想这可能被视为我的失败。这就是人生 :)。如果您打算将 C++ 编程作为一项职业,您应该学习 Alf、Nawaz 和 sbi 等人向您指出的一些内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-09
  • 2020-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-11
相关资源
最近更新 更多