返回首页

简介
本文介绍的方法,以消除冗余内存在C运算符返回一个新的对象实例复制。背景
有一件事一直懊恼有关经营者,我在C类是产生多余的内存复制的倾向。如果该实例包含了大量的数据,如一个大矩阵,这个问题变得更为严重。
比方说,你有一个MyType的运营商,在你的类(常量MyTypeamp其他)。运营商需要返回MyType的,这是很容易的一个实例,你刚才宣布在函数体之一,它初始化,并返回它。
但..当您返回该实例,它超出范围,因为它的地方,所以C复制你已经调用拷贝构造函数初始化的数据。你的对象有再兴建!
这是我指的是多余的内存拷贝。找到一个解决办法
绕过这个复制,您的运营商可以返回一个实例,因为它是正在建设中,像这样:

return MyType( SomeParameter ); 

这是甜蜜的,现在你摆脱了调用拷贝构造,和你在你快乐的方式..除非你的类需要大量的数据进行初始化。
比方说,你的类是一个矩阵类(简体):{C}
您的运营商中的每个元素添加在右手实例左手实例,初始化的实例并返回结果。你想想位,并找出可以计算出结果,在一个临时数组,并使用一个构造函数作为输入您的阵列。
Mat8x8::Mat8x8( const double* data )  { memcpy( M, data, 512 ); }



Mat8x8::Mat8x8 operator + ( const Mat8x8& other ) {

    double Sum[64];

    for( int i=0; i<64; ++i )

        Sum[i] = M[i] + other.M[i];

    return Mat8x8( Sum );

    }

好了,至少你不会有两次来构造您的实例,但你不过去的初始化数据复制。您建立一个内存块被复制到彗星决定使用对象实例的内存。你真正想要的是一个直入记忆体编译器决定使用您的实例的方式来建立你的结果。但你怎么能这样做呢?您无法计算结果,因为你不知道放在哪里,你不能让地方把它,因为你没有什么传递到c'tor。
有些人试图解决这个问题,通过定义类的"新"安置,使他们能够得到c'tor建立在内存中,他们希望,但是,当然,只有工作动态分配的实例。必须有一些其他的方式。这给我们带来了"我的路"...
好老的函数指针!
下面的代码清单体现了方法,并应为自己说话。一个特殊的构造函数接受一个函数指针和指针的左手和右手操作数。回调函数对经营者的实际工作,一旦C已决定把你的对象的实例。
class Mat8x8 {

    //<span class="code-comment">

</span>    //<span class="code-comment"> The callback function does the actual work for the operator.

</span>    //<span class="code-comment">

</span>    typedef void (*PFnInitMat)( Mat8x8& Mat, void* pLHS, void* pRHS );

    //<span class="code-comment">

</span>    //<span class="code-comment"> The special constructor takes a function pointer

</span>    //<span class="code-comment"> and pointers to the left hand and right hand operands.

</span>    //<span class="code-comment">

</span>    Mat8x8( PFnInitMat Init, void* pLHS, void* pRHS );

public:

    union {

    double  M[8][8];

    double  A[ 64 ];

    };

    Mat8x8()                      { memset( A, 0, 64*sizeof(double) ); }

    Mat8x8( const Mat8x8& other ) { memcpy( A, other.A, 512 ); }



    Mat8x8  operator + ( const Mat8x8& rhs );

    Mat8x8  operator / ( double rhs );

    //<span class="code-comment"> : etc..

</span>};



typedef Mat8x8* PMat8x8;

typedef double* pdouble;



//<span class="code-comment"> The special constructor just forwards the work to the callback function.

</span>Mat8x8::Mat8x8( PFnInitMat Init, void* pLHS, void* pRHS )

{

    Init( *this, pLHS, pRHS );

}



//<span class="code-comment"> An "operation" callback to add two matrices.

</span>void AddMat88( Mat8x8& Mat, void* pLHS, void* pRHS )

{

    //<span class="code-comment"> These two references are not necessary, but makes reading easier.

</span>    Mat8x8& lhs = (Mat8x8&) *PMat8x8( pLHS );

    Mat8x8& rhs = (Mat8x8&) *PMat8x8( pRHS );



    for( int r=0; r < 8; ++r )

        for( int c=0; c < 8; ++c )

            Mat.M[r][c] = lhs.M[r][c] + rhs.M[r][c];

}



//<span class="code-comment"> An "operation" callback to divide a matrix by a scalar.

</span>void DivMat88scalar( Mat8x8& Mat, void* pLHS, void* pRHS )

{

    for( int i=0; i < 64; ++i )

        Mat.A[i] = PMat8x8( pLHS )->A[i] / *pdouble( pRHS );

}



//<span class="code-comment"> The operators delegate the work via a function pointer.

</span>Mat8x8 Mat8x8::operator + ( const Mat8x8& rhs )

{

    return Mat8x8( AddMat88, this, (void*)&rhs );

}



Mat8x8 Mat8x8::operator / ( double rhs )

{

    return Mat8x8( DivMat88scalar, this, (pdouble)&rhs );

}
尤里卡!
没有更多的初始化数据的移动。您通过几个指针。
)在您的运营商,你只需要调用的特殊构造,并把它传递一个函数指针和指针,以左手和右手的实例。 C发现您的实例块,并调用初始化程序,现在可以建立直入记忆体编译器选择的结果。
所有的运营商可以使用相同的技术,你只写了合适的操作功能,您可以传递给构造函数。后记
您可能认为这摆脱一个小数据传输做了很多,但如果你用几十或几百字节,大矩阵处理?或者你处理大量的实例吗?小东西加起来。我希望你会发现这种技术非常有用,和你的程序将获得更快的..
我一定会继续使用"我的路"。使用代码
为了使这一计划的类型安全,你可以改变的回调函数类型的,而不是void指针的指针或引用,并使用几种不同的回调函数类型来处理不同的操作数类型。我只是用这个例子无效* - 他们使编程灵​​活(下责任重大,)。
另一方面,你可以有一个共同的回调函数的签名,您使用许多类,以及它们转换成你想要的..选择是你的。特殊的构造,绝对不应被公众虽然。兴趣点
这是一个众所周知的,有点伤心,事实上,你不能调用在C的构造函数的虚拟方法。然而,你可以使用同样的方法,来解决某些情况下你的构造不能预先知道如何处理一些初始化。历史原始的出版物,2010年4月

回答

评论会员:Aescleal 时间:2012/01/26
这就像一个模拟(N)RVO(CFront 3.0执行),R值引用(C为0x)和平普尔特别令人费解的方式(在1991年首次记录我的书架上似乎与真的很受欢迎,在2000年香草萨特)。

你可以从几乎任何编译器这是自2000年以来发布的算术运算算术赋值运算符,如转发相当的性能:

T operator ( const T &a, const T &b)

{

    T c( a );

    return c += b;

}

其中T是平普尔。

(平普尔是一个非常可怕的一个处理/身体类的名称。)

我也有点不安,看到C风格的铸造,memset和memcpy的(使用std::填写和std::复制,您不必做任何sizeofs)内置阵列(使用std::vector的) - 它使我怀疑如果作者已过去15年在语言的发展。

最后一点,安置new和delete可以使用任何的内存块,有没有参与的动态内存管理的含义。他们只是兴建任意内存位置的对象的方法
评论会员:爱奈斯特龙 时间:2012/01/26
对不起,您觉得不安地看到C代码

你提到的技术没有产生可接受的代码与我坚持的编译器(ca.1999)。模板生成丑陋的代码膨胀,所以自然
在C STD lib是出了问题(我从来没有
喜欢它,无论如何,我有更好的东西,我自己的,
它运行STD库周围的圆圈)。 {S0}

这种技术可能被复杂的,但是这
通常情况下,当你去引擎盖下,
和我说,这是一个另类的地方
编译器不RVO或其他花哨东西,懒惰的程序员。
关于

评论会员:Aescleal 时间:2012/01/26
滑稽,我用六编译器不生成使用模板和标准库通常会产生良好的代码,最好的C程序员
"巨大的代码膨胀"
不过,如果你坚持与旧的编译器(如GCC 2.95),你不能改变它(因为你做旧系统的嵌入式编程),那么你可能是正确的。不知道,我相信它。

如果RVO是懒惰的程序员花哨的东西,我讨厌,以满足您的代码的手术相当于 - 由叉勺circumcission 修改,2010年6月12日,星期六下午02:02
评论会员:merrykid 时间:2012/01/26
不擅长英语
你能告诉意味着什么这个词是什么意思?
感谢
评论会员:爱奈斯特龙 时间:2012/01/26
哦..我们对此深感抱歉。

C'tor是程序员行话,"构造"。同样,德TOR是"析构函数"短。
单引号是标准的英语语法来表示
一个或多个字符缩写。
您看到的缩写,它通常像"不",
这是"不"。有一个例外,
单引号的规则是"从不",这应该被写入"n'ever",因为它是"有史以来"短。
关于
/ /爱
评论会员:Stefan63 时间:2012/01/26
谢谢您分享您的想法。这其实是一个问题,我一直在挣扎一段时间,模板和局部静态变量的帮助下试图修复它,但它没有工作可靠(在后一种情况下,可能会导致一些颇为尴尬运行时间问题)

目前,我坚持2003年与VC(但幸运的是我们队终于获得批准今年晚些时候升级)。通过测试,我发现,没有代表的编译器的优化。这个版本反正

我也有一个问题,正如你所说,不能内部构造使用的虚拟功能。我已经解决了,但在一个比较尴尬的方式,真的不改善代码的可读性。我会在看看你的技术是否可能有助于找到更好的解决方案(我认为这将)

所以,再次感谢你的见解。
其他尽可能多的可能不明白,但我肯定。
欢呼声,
斯特凡
评论会员:爱奈斯特龙 时间:2012/01/26
谢谢您,
我很高兴,如果这种帮助的您解决问题。然后有一个点,在它出版

好运
/ /爱
评论会员:游客 时间:2012/01/26
are_all_nicks_taken_or_what
!编译器完成这个
评论会员:爱奈斯特龙 时间:2012/01/26
我不,我不能,我不会
并非所有的世界中起着与微软玩具

许多编译器,包括GCC和VC,使用NRVO
处理这一问题。文章介绍
一个通用的,独立的编译器,替代
NRVO
评论会员:wtwhite 时间:2012/01/26
您的解决方案肯定是一个有用的方法得到的东西在普遍部署的编译器版本工作。但MS的编译器是不是唯一的编译器支持右值引用({A}必要的。

但正如我所说,有正当理由(尤其是兼容性),所以可能无法移动到C为0x,这是一个很好的解决方案在这种情况下
评论会员:爱奈斯特龙 时间:2012/01/26
谢谢你,

是的,我认为这是非常有用的,编译器不
返回值优化的支持和/或右值引用。
即使这样,有时会被替代。

毕竟,移动语义仍是一个比较新的概念,
而且许多编译器甚至没有NRVO,更不用说右值
引用,我是一个坚持旧的编译器。BR}
这就是为什么我想出了这个解决方案摆在首位。

关于
/ /爱
评论会员:dpisarciuc 时间:2012/01/26
没错,你说得对,MS VC生成所需的代码为您妥善处理这类案件。但是... ...不是所有的编译器不一样...

我可以提出更好的解决方案,我认为这是共享数据containters ...
这样的一个例子是在MFC中的CString的实施。其主要思想是,容器处理结构,其中包含的原始数据和文献。计数器。所有的复制功能,只使用该结构的指针,递增/递减的文献。计数器。
返回值一个庞大的数据对象是一个复制一个指针和INC / DEC ref.counter的问题。转变职能的所有数据之前写的方法是使用副本。

QMap QList,所有Qt容器...数据共享容器...

该代码是好得多...
评论会员:亚历山大GRANVAUD 时间:2012/01/26
我想(如果我理解正确)R值运营商解决这个问题在2010年的Visual C
评论会员:凯文Drzycimski 时间:2012/01/26
听起来不错,
但是这不是你的编译器,即所谓的(命名)的返回值优化
什么约meassurements? 问候
评论会员:维迪奇Trifunovic 时间:2012/01/26
没错!
的VC不RVO,即使在调试版本。
同时,我们有]现在,照顾的情况下,当编译器不能够执行复制elisions。
UTF8 - CPP
评论会员:洛夫奈斯特龙 时间:2012/01/26
是,NRVO将处理的根本问题,
但它不支持所有的编译器(如煤矿),
在有些情况NRVO将一命呜呼

我目前的技术是完全通用的,
因此它可以使用任何编译器的。
我不知道你所说的测量值的平均值。如果你的意思是性能的方法,你
估计它的实用性在
您的对象的数据大小与多少
数据的方法是通过

该方法通过从
三分返回构造函数调用,这是12个字节
(32位拱),然后构造函数
转发这些指针,这两个
另外8个字节,再有就是
返回地址的初始化调用,
这是另一个4。 24个字节的总开销。

如果你的对象包含的数据比少,
使用这种方法,你会受到惩罚
但如果你携带超过24个字节的数据
(3双打),您可以按比例获得。

我不会理会使用小于
5-6双打。我目前使用的3x3
在我的物理和4x4双精度矩阵
模拟引擎。他们是超级精简
速度。

/ /爱
评论会员:Ejaz 时间:2012/01/26