24小时热门版块排行榜    

CyRhmU.jpeg
查看: 1703  |  回复: 6

朱biyun

铜虫 (小有名气)

[求助] VC++中函数返回数组指针或者带指针的结构体的编译方式是否可取?

偶以前经常采用这种方式来进行C++的编程,只是在用delete释放内存时需要小心一些。
用这种方式编程的一个好处在于,比如计算矩阵运算,例如A+B时,可采用运算符+的重载来进行,这样在使用程序包时会很方便。
可最近喜欢在结构体中的使用析构函数,发现有个问题,就是子函数返回结构体时,会自动执行析构函数(即使当此结构体为外部变量时依然如此),因此把结构体中的指针内存提早释放,导致返回的结构体中的指针也相应被释放,最终出错。

我看书中的程序范例在计算矩阵或者多项式时,都不是采用运算符重载,而是用常规的函数,把所用用到的结构体都用加&的方式赋过来,才不会被析构函数释放,可这种操作显然太麻烦?但不知道是不是以前我的编程方式,本身就是一种错误的习惯,还是说干脆放弃使用析构函数,每次手动释放内存?可是有没有更好的方式可以二者兼得呢?
回复此楼

» 猜你喜欢

» 本主题相关价值贴推荐,对您同样有帮助:

已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

libralibra

至尊木虫 (著名写手)

骠骑将军

【答案】应助回帖

感谢参与,应助指数 +1
构造函数+析构函数应该是比较正规的做法吧,
你的结构体/类被提早释放的问题,是不是那个结构体是局部变量,当超出作用域时对象被销毁,如果存在析构函数,会被默认调用,
如果你返回结构体/类的指针,按理不会被自动析构的
你最好贴一段出问题的代码来看看
matlab/VB/python/c++/Java写程序请发QQ邮件:790404545@qq.com
2楼2012-07-07 18:58:14
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

nebulaly

木虫 (著名写手)

【答案】应助回帖

★ ★
感谢参与,应助指数 +1
朱biyun: 金币+2, 有帮助, 内容好多~~ 2012-07-20 15:58:15
请看Scott Meyers《50法》中的相关章节
3楼2012-07-08 01:54:06
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

朱biyun

铜虫 (小有名气)

引用回帖:
2楼: Originally posted by libralibra at 2012-07-07 18:58:14
构造函数+析构函数应该是比较正规的做法吧,
你的结构体/类被提早释放的问题,是不是那个结构体是局部变量,当超出作用域时对象被销毁,如果存在析构函数,会被默认调用,
如果你返回结构体/类的指针,按理不会被自动析构 ...

matrix operator+(matrix& A)  //矩阵加法
  {
    int i,j;
        ANSm.build(m,n);
        if(n!=A.n||m!=A.m)
        {
          printf("\nERROR!!!\n行列不对应的两个矩阵不能相加。";
          return ANSm;
        }
        for(i=0;i           for(j=0;j             ANSm.M[j]=M[j]+A.M[j];
        return ANSm;
  }

这是定义在结构体matrix中的一个成员,用于计算矩阵加法,其中m,n分别表示矩阵的行与列,二重指针M对应矩阵的二维数组,对象ANSm是在外部定义的,在返回ANSm后,它就自动执行了ANSm中的析构函数,因此把返回结构体中的指针也释放了。

目前偶的一个办法就是在结构体中增加一个判定变量,使得析构函数在主函数main结束前,不能释放ANSm中的指针。
4楼2012-07-08 09:39:47
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

nebulaly

木虫 (著名写手)

引用回帖:
4楼: Originally posted by 朱biyun at 2012-07-08 09:39:47
matrix operator+(matrix& A)  //矩阵加法
  {
    int i,j;
        ANSm.build(m,n);
        if(n!=A.n||m!=A.m)
        {
          printf("\nERROR!!!\n行列不对应的两个矩阵不能相加。";
          return ANSm;
        }
         ...

条款23: 必须返回一个对象时不要试图返回一个引用
据说爱因斯坦曾提出过这样的建议:尽可能地让事情简单,但不要过于简
单。在C++语言中相似的说法应该是:尽可能地使程序高效,但不要过于高效。
一旦程序员抓住了“传值”在效率上的把柄(参见条款22),他们会变得
十分极端,恨不得挖出每一个隐藏在程序中的传值操作。岂不知,在他们不懈
地追求纯粹的“传引用”的过程中,他们会不可避免地犯另一个严重的错误:
传递一个并不存在的对象的引用。这就不是好事了。
看一个表示有理数的类,其中包含一个友元函数,用于两个有理数相乘:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
private:
int n, d; // 分子和分母
friend
const Rational // 参见条款21:为什么
operator*(const Rational& lhs, // 返回值是const
const Rational& rhs)
};
inline const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
很明显,这个版本的operator*是通过传值返回对象结果,如果不去考虑对
象构造和析构时的开销,你就是在逃避作为一个程序员的责任。另外一件很明
显的事实是,除非确实有必要,否则谁都不愿意承担这样一个临时对象的开销。
那么,问题就归结于:确实有必要吗?
答案是,如果能返回一个引用,当然就没有必要。但请记住,引用只是一
个名字,一个其它某个已经存在的对象的名字。无论何时看到一个引用的声明,
就要立即问自己:它的另一个名字是什么呢?因为它必然还有另外一个什么名
字(见条款M1)。拿operator*来说,如果函数要返回一个引用,那它返回的必
须是其它某个已经存在的Rational 对象的引用,这个对象包含了两个对象相乘
的结果。
但,期望在调用 operator*之前有这样一个对象存在是没道理的。也就是说,
如果有下面的代码:
Rational a(1, 2); // a = 1/2
Rational b(3, 5); // b = 3/5
Rational c = a * b; // c 为 3/10
期望已经存在一个值为 3/10 的有理数是不现实的。如果operator* 一定要
返回这样一个数的引用,就必须自己创建这个数的对象。
一个函数只能有两种方法创建一个新对象:在堆栈里或在堆上。在堆栈里
创建对象时伴随着一个局部变量的定义,采用这种方法,就要这样写operator*:
// 写此函数的第一个错误方法
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
这个方法应该被否决,因为我们的目标是避免构造函数被调用,但 result
必须要象其它对象一样被构造。另外,这个函数还有另外一个更严重的问题,
它返回的是一个局部对象的引用,关于这个错误,条款31 进行了深入的讨论。
那么,在堆上创建一个对象然后返回它的引用呢?基于堆的对象是通过使
用new 产生的,所以应该这样写operator*:
// 写此函数的第二个错误方法
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational *result =
new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
首先,你还是得负担构造函数调用的开销,因为 new 分配的内存是通过调
用一个适当的构造函数来初始化的(见条款5 和M8)。另外,还有一个问题:
谁将负责用delete 来删除掉new 生成的对象呢?
实际上,这绝对是一个内存泄漏。即使可以说服 operator*的调用者去取函
数返回值地址,然后用delete 去删除它(绝对不可能——条款31 展示了这样
的代码会是什么样的),但一些复杂的表达式会产生没有名字的临时值,程序员
是不可能得到的。例如:
Rational w, x, y, z;
w = x * y * z;
两个对 operator*的调用都产生了没有名字的临时值,程序员无法看到,因
而无法删除。(再次参见条款31)
也许,你会想你比一般的熊——或一般的程序员——要聪明;也许,你注
意到在堆栈和堆上创建对象的方法避免不了对构造函数的调用;也许,你想起
了我们最初的目标是为了避免这种对构造函数的调用;也许,你有个办法可以
只用一个构造函数来搞掂一切;也许,你的眼前出现了这样一段代码:operator*
返回一个“在函数内部定义的静态Rational 对象”的引用:
// 写此函数的第三个错误方法
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
static Rational result; // 将要作为引用返回的
// 静态对象
lhs 和rhs 相乘,结果放进result;
return result;
}
这个方法看起来好象有戏,虽然在实际实现上面的伪代码时你会发现,不
调用一个Rational 构造函数是不可能给出result 的正确值的,而避免这样的调
用正是我们要谈论的主题。就算你实现了上面的伪代码,但,你再聪明也不能
最终挽救这个不幸的设计。
想知道为什么,看看下面这段写得很合理的用户代码:
bool operator==(const Rational& lhs, // Rationals 的operator==
const Rational& rhs); //
Rational a, b, c, d;
...
if ((a * b) == (c * d)) {
处理相等的情况;
} else {
处理不相等的情况;
}
看出来了吗?((a*b) == (c*d)) 会永远为true,不管a,b,c 和d 是什么值!
用等价的函数形式重写上面的相等判断语句就很容易明白发生这一可恶行
为的原因了:
if (operator==(operator*(a, b), operator*(c, d)))
注意当 operator==被调用时,总有两个operator*刚被调用,每个调用返回
operator*内部的静态Rational 对象的引用。于是,上面的语句实际上是请求
operator==对“operator*内部的静态Rational 对象的值”和“operator*内部的
静态Rational 对象的值”进行比较,这样的比较不相等才怪呢!
幸运的话,我以上的说明应该足以说服你:想“在象operator*这样的函数
里返回一个引用”实际上是在浪费时间。但我没幼稚到会相信幸运总会光临自
己。一些人——你们知道这些人是指谁——此刻会在想,“唔,上面那个方法,
如果一个静态变量不够用,也许可以用一个静态数组⋯⋯”
请就此打住!我们难道还没受够吗?
我不能让自己写一段示例代码来太高这个设计,因为即使只抱有上面这种
想法都足以令人感到羞愧。首先,你必须选择一个n,指定数组的大小。如果
n 太小,就会没地方储存函数返回值,这和我们前面否定的那个“采用单个静
态变量的设计”相比没有什么改进。如果n 太大,就会降低程序的性能,因为
函数第一次被调用时数组中每个对象都要被创建。这会带来n 个构造函数和n
个析构函数的开销,即使这个函数只被调用一次。如果说"optimization"(最优
化) 是指提高软件的性能的过程, 那 么 现 在 这 种 做 法 简 直 可 以 称 为
"pessimization"(最差化)。最后,想想怎么把需要的值放到数组的对象中以及
需要多大的开销?在对象间传值的最直接的方法是通过赋值,但赋值的开销又
有多大呢?一般来说,它相当于调用一个析构函数(摧毁旧值)再加上调用一
个构造函数(拷贝新值)。但我们现在的目标正是为了避免构造和析构的开销啊!
面对现实吧:这个方法也绝对不能选用。
所以,写一个必须返回一个新对象的函数的正确方法就是让这个函数返回
一个新对象。对于Rational 的operator*来说,这意味着要不就是下面的代码(就
是最初看到的那段代码),要不就是本质上和它等价的代码:
inline const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
的确,这会导致“operator*的返回值构造和析构时带来的开销”,但归根
结底它只是用小的代价换来正确的程序运行行为而已。况且,你所担心的开销
还有可能永远不会出现:和所有程序设计语言一样,C++允许编译器的设计者
采用一些优化措施来提高所生成的代码的性能,所以,在有些场合,operator*
的返回值会被安全地除去(见条款M20)。当编译器采用了这种优化时(当前
大部分编译器这么做),程序和以前一样继续工作,只不过是运行速度比你预计
的要快而已。
以上讨论可以归结为:当需要在返回引用和返回对象间做决定时,你的职
责是选择可以完成正确功能的那个。至于怎么让这个选择所产生的代价尽可能
的小,那是编译器的生产商去想的事。
5楼2012-07-10 23:41:18
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

流亡诗人

新虫 (初入文坛)

【答案】应助回帖

★ ★ ★
朱biyun: 金币+3, ★★★很有帮助, 谢谢 2012-07-20 15:56:30
【VC++中函数返回数组指针或者带指针的结构体的编译方式是否可取?】 题目上问的那两个问题:

变量是具有生存周期的。在某个函数中声明、且定义的变量(纠结,这个问题让我想起了extern中的declares but does not define,- -||||黑线),都会在函数return之后结束生存周期,无论那个变量是指针还是结构体。

如果你的结构体(class example)重载了‘=’,能够赋值,那么函数返回值为普通结构体,做法可行。
返回带指针的结构体,要防止指针指向的内存,因结束生命周期而失效。(尤其是指向函数变量的,“过期就作废”;但是用new, malloc开辟空间的不会)。

我主要是做图像方面。会用到很多矩阵。图像矩阵类cvMat操作的时候,基本上都是用指针。
cvMat* cvCreateMat ( int rows, int cols, int type ); 是建立矩阵。返回值是指针。
例如mat1 = cvCreateMat( 3, 4, sizeof(char) )
无论执行什么函数,最通常的做法就是,把mat1指针各种传递。
6楼2012-07-19 17:20:57
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

神气活现

铁虫 (初入文坛)

既然是c++,这种做法没有必要。晦涩且容易出错。
smartpoint 就是为了解决这些问题的,楼主不妨看看。

我个人的做法是,自定义一个类用来管理自身,然后派生之。
比楼上多一个内存管理。这样易于优化。
7楼2013-08-16 19:50:27
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖
相关版块跳转 我要订阅楼主 朱biyun 的主题更新
信息提示
请填处理意见