在c ++中,您可以在堆和堆栈上创建类的新实例。当重载一个操作符时,你能够以一种有意义的方式在堆栈上实例化吗?
据我所知,一旦函数执行完毕,就会删除堆栈上的实例。这使得好像返回一个坐在堆栈上的新实例会有问题。
我写这篇文章知道必须有办法,但我不确定最佳做法是什么。 如果我有一些设计为始终驻留在堆栈中的类,我该如何处理运算符重载?
任何信息都会有所帮助,谢谢
{编辑} 我正在重载+运算符。 现在我使用这段代码
Point Point::operator+ (Point a)
{
Point *c = new Point(this->x+a.x,this->y+ a.y);
return *c;
}
我对如此实例化c持怀疑态度:
Point c(this->x + a.x, this->y, a.y);
因为那会将它分配给堆栈。我担心的是,一旦这个函数完成执行,堆栈指针就会改变,并且实例将不再安全,因为定义的任何新的局部变量都可以擦除它。这不是一个问题吗?
答案 0 :(得分:12)
如果您正在谈论例如operator+
,其中返回的对象不是那些输入中的任何一个,那么答案是您在堆栈上实例化并按值返回:
struct SomeClass {
int value;
};
SomeClass operator+(const SomeClass &lhs, const SomeClass &rhs) {
SomeClass retval;
retval.value = lhs.value + rhs.value;
return retval;
}
或
class SomeClass {
int value;
public:
SomeClass operator+(const SomeClass &rhs) const {
SomeClass retval;
retval.value = this->value + rhs.value;
return retval;
}
};
甚至:
class SomeClass {
int value;
public:
SomeClass(int v) : value(v) {}
friend SomeClass operator+(const SomeClass &lhs, const SomeClass &rhs) {
return SomeClass(lhs.value + rhs.value);
}
};
编译器然后担心返回值实际存储在哪里(在堆栈上)。
例如,如果可以的话,它将应用返回值优化,但原则上发生的是“as-if”你所做的工作在你的运算符重载的堆栈上构造一些值,然后在返回时将其复制到它需要在哪里下一个。如果调用者分配了返回值,则将其复制到那里。如果调用者通过值将其传递给某个其他函数,则会将其复制到调用约定所需的任何位置,以便成为该函数参数。如果调用者采用const引用,则将其复制到隐藏在堆栈上的临时引用。
答案 1 :(得分:3)
关于堆栈上的对象一旦超出范围就被销毁是对的。
但是你忽略了C ++将使用临时对象是必要的。您必须了解编译器何时创建临时变量(然后进行优化)才能使代码生效。
请注意,在下文中,我描述了一个非常简化的“纯粹”观点:编译器可以并且将会进行优化,其中,将删除无用的临时工......但行为仍然是相同的。 / p>
让我们慢慢开始:当您使用整数时应该发生什么:
int a, b, c, d ;
// etc.
a = b + (c * d) ;
上面的代码可以写成:
int a, b, c, d ;
// etc.
int cd = c * d ;
int bcd = b + cd ;
a = bcd ;
当您使用“按值”传递的参数调用函数时,编译器将为其创建一个临时副本(调用复制构造函数)。 如果从“按值”函数返回,编译器将再次制作它的临时副本。
让我们想象一下类型为T的对象。以下代码:
T foo(T t)
{
t *= 2 ;
return t ;
}
void bar()
{
T t0, t1 ;
// etc.
t1 = foor(t0) ;
}
可以写成以下内联代码:
void bar()
{
T t0, t1 ;
// etc.
T tempA(t1) // INSIDE FOO : foo(t0) ;
tempA += 2 ; // INSIDE FOO : t *= 2 ;
T tempB(tempA) // INSIDE FOO : return t ;
t1 = tempB ; // t1 = foo...
}
因此,尽管您不编写代码,但是从函数调用或返回将(可能)添加大量“不可见代码”,需要将数据从堆栈的一个级别传递到下一个/上一个级别。
同样,你需要记住,C ++编译器会最大限度地优化掉,所以可以被视为一个无效的过程只是一个想法,没有别的。
您的代码将泄漏:您“新”了一个对象,并且不删除它。
尽管您有疑虑,但正确的代码应该更像:
Point Point::operator+ (Point a)
{
Point c = Point(this->x+a.x,this->y+ a.y) ;
return c ;
}
使用以下代码:
void bar()
{
Point x, y, z ;
// etc.
x = y + z ;
}
将生成以下伪代码:
void bar()
{
Point x, y, z ;
// etc.
Point tempA = z ; // INSIDE operator + : Point::operator+ (Point a)
Point c = z ; // INSIDE operator + : Point c = Point(this->x+a.x,this->y+ a.y) ;
Point tempB = c ; // INSIDE operator + : return c ;
x = tempB ; // x = y + z ;
}
你赚得太多临时工。当然,编译器可能会删除它们,但是,不需要采取草率的习惯。
您至少应该将代码编写为:
inline Point Point::operator+ (const Point & a)
{
return Point(this->x+a.x,this->y+ a.y) ;
}
答案 2 :(得分:2)
你已经有了一些很好的答案。以下是我想补充的几点:
Point
个对象。因为它们比内置类型更大(从你的代码我认为它们包含两个内置函数),在大多数体系结构中复制它们比每个引用传递它们更昂贵。这会将您的操作符更改为:Point Point::operator+ (Point
&
)
(请注意,您必须复制结果,因为没有地方可以持久存储,因此您可以传递给它的引用。)const
引用传递它:Point Point::operator+ (
const
Point&)
。 operator+()
(除了,例如,operator+=()
)也没有更改其左参数,因此您应该让编译器检查它。对于作为成员函数的二元运算符,左参数是this
指针指向的参数。要使this
成员函数中的常量,必须在成员函数签名的末尾注入const
。这样做:Point Point::operator+ (const Point&)
const
。现在你的操作符通常被称为const-correct。 operator+()
时,人们会期望operator+=()
也存在,所以通常您应该同时实现这两个类型。由于它们的行为非常相似,为了不冗余,你应该在另一个之上实现一个。最简单,最有效(以及更多或更少规范)的方法是在+
之上实现+=
。这使得operator+()
非常容易编写 - 而且更重要的是:基本上它对于您实现它的每种类型看起来都是一样的:由于operator+()
变得非常简单,您可能希望inline
它。这将是到目前为止的结果代码:
inline Point Point::operator+ (const Point& rhs) const
{
Point result(this);
result += a;
return result;
}
这些是一些基本的句法和语义特征,(希望)所有人都会同意这一点。现在这里有一个经验法则,我用于我的代码,我觉得非常有帮助,但可能不是每个人都会同意:
后者的原因(以operator+=()
为例)非常简单:为了改变它,他们可能需要访问左参数的内部。更改类对象的内部结构最好通过成员函数完成。
前者的原因并不那么简单。除其他事项外,Scott Meyers had an excellent article解释说,与普遍看法相反,使用非成员函数通常实际上增加封装。但是事实上,对于成员函数的this
参数,一些规则(隐式转换,动态分派等)与其他参数的规则不同。由于您希望两个参数得到平等对待,因此在某些情况下,将不同的规则应用于左侧可能会令人惊讶。
然后代码如下:
inline Point operator+ (const Point& lhs, const Point& rhs) const
{
Point result(lhs);
result += rhs;
return result;
}
对我来说,这是它的最终规范形式,我在我的代码中写下来,不管它是什么类型都没有多少思考。
实施operator+=()
留给读者练习。 :)
答案 3 :(得分:0)
数据和代码是正交概念。让Code对Heap中的对象进行处理而不是驻留在堆栈上的对象有什么区别? (在两种情况下都提供尊重对象范围)
答案 4 :(得分:0)
正确执行函数时,堆栈上的数据无效是正确的。但是,在堆栈上返回数据副本是完全可以的(这就是你正在做的事情)。只需确保不返回指向堆栈数据的指针。
答案 5 :(得分:0)
使用您的代码:
Point Point::operator+ (Point a)
{
Point result(this->x+a.x,this->y+ a.y);
return result;
}
这样可以正常工作 基本上它创建结果localy(在堆栈上)。但是return语句将结果复制回调用点(就像int一样)。它使用Point拷贝构造函数将值复制回调用点。
int main()
{
Point a(1,2);
Point b(2,3);
Point c(a + b);
}
这里operator +在堆栈上创建一个本地。这将通过返回复制回调用点(c的构造函数)。然后使用c的复制构造函数将内容复制到c。
但是你认为复制结构似乎有点代价。技术上是的。但是允许编译器优化掉额外的拷贝结构(所有现代编译器都非常擅长)。
返回您的代码。
Point Point::operator+ (Point a)
{
Point *c = new Point(this->x+a.x,this->y+ a.y);
return *c;
}
不要这样做。这里您已动态分配,但您将结果复制回调用点(如上所述使用复制构造函数)。因此,当时间控制返回到调用点时,您丢失了指针并且无法取消分配内存(因此内存泄漏)。
Java和C ++之间的区别在于,当我们返回指针时,我们使用智能指针来帮助调用者识别谁负责释放内存(查找指针所有权)。