【问题标题】:Constructor being called when trying to use strtok尝试使用 strtok 时调用构造函数
【发布时间】:2020-09-13 03:27:43
【问题描述】:

我编写了一个名为 DSString 的自定义字符串类,其中包含一个私有成员 char* 数据。我正在尝试解析 csv 文件中的行,将文件行中找到的每个单词添加到 DSString 对象的 std::set 中。我调用 getline 将文件中的一行读入 char* 缓冲区,然后调用 strtok 来获取单词。但是,下面的 while 循环不起作用;我的调试器告诉我,在到达那行代码时,它会在 DSString.h 中使用参数 NULL 调用我的构造函数,然后程序结束。

int main (int argc, char ** argv) {
    
    ifstream input;
    input.open(argv1);

    int reviewLinesCtr = 0;
    char line[16000];

    set <DSString>positiveWordSet;

    while (input.getline(line,16000)) {
        if (reviewLinesCtr == 10) break;

            //Push review to positive set
            DSString word = strtok(line, " ");
            while (word != nullptr) {         //Program ends abruptly here
                positiveWordSet.insert(word);
                word = strtok(nullptr, " ");
            }
        }
        reviewLinesCtr++;
    }

    return 0;
}

DSString 只有私有成员 char *dataint size。 这是DSString中的相关代码:

    DSString::DSString(const char* param) {  //This is the constructor being  passed, param = NULL, which crashes the program.
        data = new char[strlen(param) + 1];
        this->size = strlen(param) + 1;
        strcpy(data, param);
    }

    DSString& DSString::operator= (const char* source)  {
        if (data != nullptr) {
            delete[] data;
        }
        data = new char[strlen(source + 1)];
        size = strlen(source) + 1;
        strcpy(data, source);
        return *this;
    }

    bool DSString::operator!= (const DSString& rhs) const {
        if (strcmp(this->data, rhs.data) != 0) {
            return true;
        }
        else {
            return false;
        }
    } 

这是大学项目的一部分,因此我们需要实现自定义 DSString 类。否则我不会使用它,而是使用 std::string。除了从文件中读取的缓冲区外,我们也不能在程序中的任何地方使用 char*。当我尝试开始执行此 while 循环时,为什么会调用此构造函数并将 NULL 作为参数?我该如何解决?

如果这是一个重复的问题,我深表歉意,我已经在互联网上搜索了一段时间,但没有找到任何有助于解释这里发生的事情的信息。 任何帮助将不胜感激。

【问题讨论】:

  • DSString word = strtok(line, " "); 将调用Copy Initialization
  • 你有什么证据证明在以下行中:word = strtok(nullptr, " ");,对strtok 的调用永远不会返回NULL,因此将空指针传递给你的构造函数或operator=,结果在观察到的段错误中? (提示:你没有证据)。附:你的operator= 有严重的内存泄漏。
  • strtok 在 C++ 中很少使用,所以,尽量不要使用它。
  • 您的赋值运算符泄漏内存。问题是您试图在应用程序中使用损坏的类,该类需要 DSString 具有正确的复制语义,而 DSString 没有正确的复制语义。
  • set&lt;DSString&gt; -- 你真的需要发布你的整个DSString 课程。假设这是std::set,就目前而言,这将惨遭失败,因为DSString 类没有正确的复制语义,因此您的运行时错误可能源于代码中的多个错误。

标签: c++


【解决方案1】:
DSString word = ....
while (word != nullptr) {         //Program ends abruptly here

在这里,您将DSString 对象与nullptr 进行比较。再来看看算子重载:

bool DSString::operator!= (const DSString& rhs) const {

它接受DSString 引用,而不是指针。但是,您的类可以从指针隐式转换,因此可以从 astd::nullptr_t 间接转换,因为它有一个转换构造函数:

SString::DSString(const char* param) {  //This is the constructor being  passed, param = NULL, which crashes the program.

由于构造函数调用 strlen(param)strcpy(data, param);,将 null 传递给该构造函数具有未定义的行为。

为避免使用隐式转换意外调用此构造函数,您应将其声明为explicit

将 null 传递给构造函数在这里也是一个问题:DSString word = strtok(line, " ");

要修复逻辑,您应该使用带有strtok 的指针,并且仅在您检查它是否为非空后才创建您的字符串:

        char* word = strtok(line, " ");

我们也不能在程序中的任何地方使用 char*,除了从文件中读取的缓冲区。

坦率地说,使用strtok 但不使用char* 只是愚蠢的要求。

好像你已经违反了这个要求:

DSString 只有私有成员 char *data 和 ...

无论如何,如果您需要遵循这样的限制,您需要更改构造函数以在将 null 传递给它时正确运行。具体来说,在这种情况下,它可能不会将该指针传递给具有未定义行为的函数。

而且,如果将成员设置为 null,那么在将成员传递给上述这些函数之前,还必须检查该成员是否为 null。

【讨论】:

  • strtok 不是必需的,它只是我能想到的完成此任务的唯一方法。我尝试使用字符串流,但也无法使其正常工作。 DSString 成员数据允许为 char* - 很抱歉有误导性的措辞。那和文件缓冲区是唯一允许为 char* 的两件事。所以我无法实现你的 char* word = strtok(line, " "); 。我现在明白为什么构造函数会这样,谢谢。但是,如果我将构造函数更改为显式,那么我也必须更改 != 运算符,对吗?同意,要求很愚蠢:)
  • @LandonWood 在您修复 DSString 类的复制语义之前,关于 strtok 以及其他成员是什么确实是一个有争议的问题。
  • @PaulMcKenzie 根据 user4581301 的回答,我编辑了 OP,我认为这是对复制分配运算符的正确修复。这是否遵循正确的复制语义?抱歉,我不熟悉这个术语以及它的确切含义......
  • 不,它没有。您的课程未能实现rule of 3。转到该链接的管理资源部分并仔细阅读。
【解决方案2】:

eeorika 的回答中很可能概述了崩溃的原因。没有必要在这里重复。

为什么要调用构造函数:

DSString word = strtok(line, " ");

Copy Initialization,所以调用的是构造函数,而不是赋值运算符。

旁注:

还有另一个致命错误。由于strtok returns nullptr在没有token的时候,DSString::DSString(const char* param)在没有token的时候会被调用

data = new char[strlen(param) + 1];

将在空指针上调用strlenKabooom.

解决方案:在const char * 变量中捕获strtok 的返回值,并对其进行nullptr 测试。只有在有令牌的情况下才进行构建。

由于赋值要求是不能使用char * 变量,请确认是否可以使用const char *。如果不是,那是一个有趣(而且非常愚蠢)的泡菜。

赋值运算符泄漏内存。

DSString& DSString::operator= (const char* source)  {
    delete[] data; // must release the object's current string
                   // before allocating a new one
    data = new char[strlen(source + 1)]; 
    size = strlen(source) + 1;
    strcpy(data, source);
    return *this;
}

【讨论】:

  • ... 并测试“nullptr”将是一个更好的建议。其余的滑动规模并不难。只需static_assert
  • @TedLyngmo 这个答案真正做的唯一有用的事情是解释为什么调用构造函数而不是赋值运算符。我想我会删除吸盘并在 eerorika 的回答下添加评论。我完全错过了while循环中的问题。以及DSString::operator=中的泄漏
  • 不要删除!我个人认为它很有用 - 我只是不知道它是否符合 OP 的问题。
  • 感谢您的回答并指出内存泄漏。我也回复了 eeororika,但我认为我也不能使用 const char *。这很愚蠢,但这是我得到的要求。否则这将更容易解决
  • @LandonWood 在您花时间解决它之前咨询您的教练以绝对确定这是一项硬性要求。任何不捕获和测试的解决方案都会教你成为一个更糟糕的程序员。也许期望是抛出一个异常,但是每次在最后一个标记处都会有一个空指针,并且总是发生的情况与它所获得的异常相去甚远。你可以有“空的”DSStrings,但是你必须在每次使用它时测试每个DSString,以确保它不是空的,这违背了构造函数的观点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多