【问题标题】:C++ How can I construct an object inside a class function, do some operations and return the value?C++ 如何在类函数中构造对象,进行一些操作并返回值?
【发布时间】:2014-04-03 06:22:51
【问题描述】:

因为是1000+行,所以我不打算过去整个源码,但是我对我感兴趣的事情专门构建了一个类似的案例。注意这个源码:

#include <iostream>
using namespace std;

class Person
{
public:
    Person();
    Person(char*);
    ~Person();
    Person& operator=(const Person&);
    friend Person& example(const Person&);
    void print() const;
private:
    char* name;
};

Person::Person()
{
    name = new char[12];
    name = "Temp";
}

Person::~Person()
{
     delete[] name;
}

Person::Person(char* _name)
{
    name = new char[strlen(_name)+1];
    strcpy_s(name,strlen(_name)+1,_name);
}

Person& example()
{
    char* TestName = new char[11];
    TestName = "ShouldStay";
    Person B(TestName);
    return B;
}

void Person::print() const
{
    cout << name;
}

int main()
{
    example();
    return 0;
}

在这种情况下,example() 函数将返回:

  • example returned {name=0x007cad88 "îþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþîþ... } Person &

显然,在返回命令上调用了析构函数并删除了堆中的内存(所以我无法对指针做任何进一步的事情 - 它指向已经释放的内存 - 没有数据)。

我的问题是 - 如何避免这种行为?避免此类问题的最优雅方法是什么?

提前谢谢你!

【问题讨论】:

  • 按值返回。遵循五个甚至更好的规则,rule of zero
  • TestName = "ShouldStay"; 也不对。
  • 请注意,您需要为此类定义(自定义)复制构造函数,以使按值返回正常工作。 (或遵循零规则;)
  • 对于任意类型的数组,字符串 "workaround" -> 向量 "workaround" :) (其中 "workaround" = 安全简单的解决方案)
  • "hello world" 这样的字符串字面量属于array of 12 const char可以转换为在早期版本的 C++ 中指向 char 的指针。这种转换在 C++ 中不再存在。由于向后兼容,它在 MSVC++ 中可能仍然存在一段时间,但不要依赖它。如果您尝试更改字符串文字的任何字符,地狱就会崩溃。至于内存泄漏:它就在那里。 MSVC 可能无法检测到它,但您分配了一些内存,然后忘记了它的地址(通过用一些字符串文字的地址覆盖它)。

标签: c++ destructor


【解决方案1】:
  1. 使用string 而不是char[] 以避免必须使用new
  2. 返回Person 而不是Person&amp;,作为本地范围的类 离开作用域后被销毁。虽然这会导致 复制取决于编译器设置。这当然取决于提供适当的复制构造函数。
  3. 为保证避免复制,请将example的签名更改为:

    void example(Person& person)
    

    并填写函数内输入的人的字段。 Person 的范围将绑定到调用范围(或您构造它的任何其他地方)。这种方法有一些缺点,例如您不能将结果链接在一起。

【讨论】:

  • 如果他按照第 1 步进行操作,那么对于示例情况而言,这无关紧要,但您使用 char[] 是正确的,默认的复制构造函数将不起作用。答案已更新以反映。
  • @dyp 另外,我的第 3 步传递引用分配不需要任何类型的编译器优化。它只要求允许example 访问Person 的字段。
  • 感谢您的回答! - 这只是真正问题的一个例子,而在真正的问题中,事情变得很糟糕:) 1) 我无法使用类似字符串的解决方法,因为在实际示例中,我正在处理对象数组,而不是带字符的数组; 2)我已经实现了复制构造函数,所以我会尝试这个方法(我只是需要一些时间来测试); 3)我无法使函数无效,因为我必须实现重载运算符+。
  • 现在可以了,但我会将 2+3 解释为一般按引用传递而不是按值返回的建议。后者应该是默认选择恕我直言,因为通常会应用返回值优化,这甚至比默认构造加传递引用更便宜;对于剩下的情况,我们现在有移动语义。
  • 这也绝对取决于 OP 试图用该方法做什么。看起来他们正在尝试进行某种链接,因此按值返回可能是正确的选择。更新了反映的答案。
【解决方案2】:

您的代码包含许多逻辑错误:

Person::Person()
{
    name = new char[12];
    name = "Temp";
}

在上面的函数中,你分配了一个包含 12 个元素的 char 数组,然后你就忘了它,而是让 name 指向一个字符串字面量。

Person::~Person()
{
     delete[] name;
}

哎呀。如果Person 是从默认构造函数构建的,那么delete 将是一个字符串文字。 C++ 中的禁忌。

Person::Person(char* _name)
{
    name = new char[strlen(_name)+1];
    strcpy_s(name,strlen(_name)+1,_name);
}

不是 100% 确定 strcpy_s 是什么,但本例中的代码分配了一个数组,并且似乎将字符串复制到数组中。这似乎没问题(但出于多种原因,只需 strcpy(name, _name); 会更好)。

Person& example()
{
    char* TestName = new char[11];
    TestName = "ShouldStay";
    Person B(TestName);
    return B;
}

此代码已严重损坏。首先,它通过引用返回一个临时对象。一个非常非常糟糕的主意。它还分配了一个数组,然后再次忘记它并使用字符串文字。

让您的代码工作的最优雅的方式(实际上是我认为唯一的方式)是首先了解 C++ 的基础知识是如何工作的。您应该首先开始 by reading a good C++ book from cover to cover,然后才应该开始使用 C++ 进行编码。

您的 1000 行源代码很可能只是垃圾。我不是说你笨,只是说你不了解 C++ 的基础知识。首先通过阅读而不是尝试编译器来处理它们。

你不能通过实验来学习 C++,原因有两个:

  • 由于其历史原因,这是一种复杂的语言,有时甚至是完全不合逻辑的语言。猜测几乎总是一个糟糕的举动。无论你多么聪明,你都无法正确猜测委员会的决定。

  • 当您犯错时,没有运行时错误天使会告诉您。经常发生的情况是,该程序显然仍然可以运行……直到它由您的老师、老板或配偶运行。通过编写代码并观察发生的情况来猜测 C++ 规则是无稽之谈。

【讨论】:

  • strcpy_s = MS“安全”版本的strcpy,缓冲区大小“检查”
  • 然后你才应该开始用 C++ 编写代码,那本书中的练习呢? ;)
  • @dyp:在这种情况下strcpy 会更好(当然,假设有理由不使用std::string)。
  • 我还认为这些错误中的大多数仅仅是来自 OP 试图发明一个简化的示例来复制他们的问题。他们非常明确地说这不是他们正在使用的代码。但正如你所说,除非你真的、真的知道你在做什么,否则在 C++ 中这几乎是毫无意义的事情,因为你的“简化”示例很可能会出现完全掩盖你试图诊断的实际问题的错误。
  • @aruisdante:x = new char[4]; x = "foo"; 模式(重复两次)清楚地表明,不了解指针是什么以及 C++ 中的内存分配是如何工作的。我的猜测是,他们正在使用的代码只是同一部恐怖电影,只不过要长得多。
【解决方案3】:
Person& example()
{
    char* TestName = new char[11];
    TestName = "ShouldStay";
    Person B(TestName);
    return B;
}

上面在堆栈上创建了一个 Person 实例,作用域为函数。因此,当函数返回时,将调用 Person 的析构函数并返回对已销毁对象(在堆栈上)的引用。

此外,您应该考虑返回 Person 的副本,或者您需要使用 new 运算符在堆上创建 person 实例并返回指向该实例的指针。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-23
    • 2013-11-20
    • 2014-10-02
    • 1970-01-01
    • 2012-07-28
    • 2011-10-14
    • 2011-11-13
    相关资源
    最近更新 更多