简介
本文介绍的方法,以消除冗余内存在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月