VS2005 から VS2010 に移行すると、ファンクターの追加コピーが原因と思われるパフォーマンスの低下に気付きました。
次のコードは、問題を示しています。値自体がセットであるマップを持つことが不可欠です。マップとセットの両方で、比較ファンクターを定義しました (例ではテンプレート化されています)。
#include <iostream>
#include <map>
#include <set>
class A
{
public:
A(int i, char c) : m_i(i), m_c(c)
{
std::cout << "Construct object " << m_c << m_i << std::endl;
}
A(const A &a) : m_i(a.m_i), m_c(a.m_c)
{
std::cout << "Copy object " << m_c << m_i << std::endl;
}
~A()
{
std::cout << "Destruct object " << m_c << m_i << std::endl;
}
void operator= (const A &a)
{
m_i = a.m_i;
m_c = a.m_c;
std::cout << "Assign object " << m_c << m_i << std::endl;
}
int m_i;
char m_c;
};
class B : public A
{
public:
B(int i) : A(i, 'B') { }
static const char s_c = 'B';
};
class C : public A
{
public:
C(int i) : A(i, 'C') { }
static const char s_c = 'C';
};
template <class X>
class compareA
{
public:
compareA() : m_i(999)
{
std::cout << "Construct functor " << X::s_c << m_i << std::endl;
}
compareA(const compareA &a) : m_i(a.m_i)
{
std::cout << "Copy functor " << X::s_c << m_i << std::endl;
}
~compareA()
{
std::cout << "Destruct functor " << X::s_c << m_i << std::endl;
}
void operator= (const compareA &a)
{
m_i = a.m_i;
std::cout << "Assign functor " << X::s_c << m_i << std::endl;
}
bool operator() (const X &x1, const X &x2) const
{
std::cout << "Comparing object " << x1.m_i << " with " << x2.m_i << std::endl;
return x1.m_i < x2.m_i;
}
private:
int m_i;
};
typedef std::set<C, compareA<C> > SetTest;
typedef std::map<B, SetTest, compareA<B> > MapTest;
int main()
{
int i = 0;
std::cout << "--- " << i++ << std::endl;
MapTest mapTest;
std::cout << "--- " << i++ << std::endl;
SetTest &setTest = mapTest[0];
std::cout << "--- " << i++ << std::endl;
}
このコードを VS2005 でコンパイルすると、次の出力が得られます。
--- 0
Construct functor B999
Copy functor B999
Copy functor B999
Destruct functor B999
Destruct functor B999
--- 1
Construct object B0
Construct functor C999
Copy functor C999
Copy functor C999
Destruct functor C999
Destruct functor C999
Copy object B0
Copy functor C999
Copy functor C999
Copy functor C999
Destruct functor C999
Destruct functor C999
Copy object B0
Copy functor C999
Copy functor C999
Copy functor C999
Destruct functor C999
Destruct functor C999
Destruct functor C999
Destruct object B0
Destruct functor C999
Destruct object B0
--- 2
これを VS2010 でコンパイルすると、次の出力が得られます。
--- 0
Construct functor B999
Copy functor B999
Copy functor B999
Destruct functor B999
Destruct functor B999
--- 1
Construct object B0
Construct functor C999
Copy functor C999
Copy functor C999
Destruct functor C999
Destruct functor C999
Copy object B0
Copy functor C999
Copy functor C999
Copy functor C999
Destruct functor C999
Destruct functor C999
Copy functor C999
Assign functor C999
Assign functor C999
Destruct functor C999
Copy object B0
Copy functor C999
Copy functor C999
Copy functor C999
Destruct functor C999
Destruct functor C999
Copy functor C999
Assign functor C999
Assign functor C999
Destruct functor C999
Destruct functor C999
Destruct object B0
Destruct functor C999
Destruct object B0
--- 2
最初のステートメント (マップを作成する) の出力は同じです。
2 番目のステートメント (マップ内の最初の要素を作成し、それへの参照を取得する) の出力は、VS2010 の場合ははるかに大きくなります。
- ファンクターのコンストラクターのコピー: 10 回対 8 回
- ファンクターの割り当て: 2 回 vs. 0 回
- functor のデストラクタ: 10 回 vs. 8 回
私の質問は次のとおりです。
- STL がファンクターをコピーするのはなぜですか? セットのインスタンス化ごとに 1 回構築するだけで十分ではないでしょうか?
- ファンクターがVS2005の場合よりもVS2010の場合に構築されるのはなぜですか? (VS2008はチェックしていません)
- また、VS2005 ではなく VS2010 で 2 回割り当てられるのはなぜですか?
- ファンクターのコピーを避けるためのトリックはありますか?
C++ ファンクター オブジェクトの不要なコピーを防止する で同様の質問を見ましたが、それが同じ質問かどうかはわかりません。