为什么 return 指向堆上变量的指针而不是 C++ 中的变量本身
Why return a pointer to a variable on the heap instead of the variable itself in C++
所以我来自 Java,现在正在学习 C++,
我明白指针是如何工作的,堆栈和堆内存是什么,我在谷歌上搜索了很多,但我似乎无法理解为什么我们不只是 return 对象本身而不是指针指向像本例中那样在堆上创建的对象:
(我明白为什么我们必须在第一个例子中分配对象在堆上而不是在堆栈上。)
class Thingy;
Thingy* foo()
{
Thingy *pointerToHeap = new Thingy();
return pointerToHeap;
}
所以来自 Java 我会这样做:
class Thingy;
Thingy foo()
{
Thingy a;
return a;
}
由于我几乎总是明白堆上的对象比堆栈上的对象寿命长的原因,所以我不明白如果我的函数也能正常工作,为什么我们会像第一个示例那样编写函数。
指针示例returns 指向对象的指针。它分配在堆上,调用者可以直接访问同一个副本。调用者还负责在某个时候从堆中删除 Thingy。
对象实例示例在堆栈上构造一个 Thingy,然后将其复制到调用者的 Thingy,假设您有一行 Thingy2 = Thingy1.foo() 调用该函数。根据对象的大小,与使用一个对象相比会有性能损失。
使用 std::shared_ptr 可能更像您习惯的 Java。它会创建该对象的一个副本,并在不再引用它时将其丢弃。您可以传递它,每个引用都将指向该对象的同一个副本。
你的两个例子不等价:
在 Java 示例中,您忘记了实际创建一个对象。
更正后的代码:
class Thingy;
Thingy foo() {
Thingy a = new Thingy();
return a;
}
Java 指针和 C++ 指针的语法不同,因为 Java 不允许堆栈分配,只允许堆分配,并且不允许非 class 类型的分配.这意味着不需要区分单级指针、多级指针和非指针变量,因为它们都是单级指针。
它们之间还有一个额外的语义差异:
虽然 C++ 确实 支持垃圾收集,但这种情况极为罕见,而在 Java 中它是强制性的。
因此,在 C++ 中,有两个更好的选择:
Return 按值,如果复制或移动便宜:
class Thingy;
Thingy foo() {
Thingy t; // Thingy t(); would declare a function instead.
return t; // The copy/move will probably be elided due to NRVO
}
Return 使用 std::unique_ptr
,明确表示所有权转移并使其异常安全:
#include <memory>
class Thingy;
std::unique_ptr<Thingy> foo() {
unique_ptr<Thingy> p = new Thingy();
return p;
}
此选项还具有不破坏 ABI 的优点,对于几乎所有平台,如果从您的版本更改。
虽然它打破了 API,但这很容易纠正。
另一种方法是返回 std::shared_ptr
,以允许使用 make_shared
:
#include <memory>
class Thingy;
std::shared_ptr<Thingy> foo() {
auto p = std::make_shared<Thingy>();
return p;
}
所以我来自 Java,现在正在学习 C++,
我明白指针是如何工作的,堆栈和堆内存是什么,我在谷歌上搜索了很多,但我似乎无法理解为什么我们不只是 return 对象本身而不是指针指向像本例中那样在堆上创建的对象:
(我明白为什么我们必须在第一个例子中分配对象在堆上而不是在堆栈上。)
class Thingy;
Thingy* foo()
{
Thingy *pointerToHeap = new Thingy();
return pointerToHeap;
}
所以来自 Java 我会这样做:
class Thingy;
Thingy foo()
{
Thingy a;
return a;
}
由于我几乎总是明白堆上的对象比堆栈上的对象寿命长的原因,所以我不明白如果我的函数也能正常工作,为什么我们会像第一个示例那样编写函数。
指针示例returns 指向对象的指针。它分配在堆上,调用者可以直接访问同一个副本。调用者还负责在某个时候从堆中删除 Thingy。
对象实例示例在堆栈上构造一个 Thingy,然后将其复制到调用者的 Thingy,假设您有一行 Thingy2 = Thingy1.foo() 调用该函数。根据对象的大小,与使用一个对象相比会有性能损失。
使用 std::shared_ptr 可能更像您习惯的 Java。它会创建该对象的一个副本,并在不再引用它时将其丢弃。您可以传递它,每个引用都将指向该对象的同一个副本。
你的两个例子不等价:
在 Java 示例中,您忘记了实际创建一个对象。
更正后的代码:
class Thingy;
Thingy foo() {
Thingy a = new Thingy();
return a;
}
Java 指针和 C++ 指针的语法不同,因为 Java 不允许堆栈分配,只允许堆分配,并且不允许非 class 类型的分配.这意味着不需要区分单级指针、多级指针和非指针变量,因为它们都是单级指针。
它们之间还有一个额外的语义差异:
虽然 C++ 确实 支持垃圾收集,但这种情况极为罕见,而在 Java 中它是强制性的。
因此,在 C++ 中,有两个更好的选择:
Return 按值,如果复制或移动便宜:
class Thingy; Thingy foo() { Thingy t; // Thingy t(); would declare a function instead. return t; // The copy/move will probably be elided due to NRVO }
Return 使用
std::unique_ptr
,明确表示所有权转移并使其异常安全:#include <memory> class Thingy; std::unique_ptr<Thingy> foo() { unique_ptr<Thingy> p = new Thingy(); return p; }
此选项还具有不破坏 ABI 的优点,对于几乎所有平台,如果从您的版本更改。
虽然它打破了 API,但这很容易纠正。另一种方法是返回
std::shared_ptr
,以允许使用make_shared
:#include <memory> class Thingy; std::shared_ptr<Thingy> foo() { auto p = std::make_shared<Thingy>(); return p; }