鉴于此类型的测试:
struct Counter
{
static int count;
Counter(int)
{
count++;
}
Counter(const Counter&)
{
count++;
}
Counter(Counter&&) noexcept
{
}
};
int Counter::count = 0;
假设我们有以下内容:
std::vector<Counter> vec(5, 0);
根据VS2015,创建了6个Counter
个对象。我知道只有5个永久对象。为什么编译器没有从构造函数参数中放置对象,或者将临时对象移动到第一个位置然后从中复制其余的对象?
即使矢量的初始大小设置为0,仍然会创建1个对象。
std::vector<Counter> vec(0, 0);
如果在运行时之前不知道大小并且通常为0(无操作)并且容器中的类型复制或构建成本昂贵,这可能很重要。
在一个语句中初始化向量通常很方便,特别是如果它们是初始化列表中的类成员或常量。我怎样才能像下面的代码一样有效地做到这一点:
std::vector<Counter> vec;
vec.reserve(size);
for (size_t i = 0; i < size; i++)
{
vec.emplace_back(0);
}
它构造的对象只包含存储在向量中的对象。
答案 0 :(得分:1)
我认为这是有效的,基于您的定义。
测试不同的版本;
Copies: 100000005 , Construct: 1, Equal copies 0
real 0m0.075s
user 0m0.073s
sys 0m0.002s
并使用emplace_back:
Copies: 0 , Construct: 100000005, Equal copies 0
real 0m0.195s
user 0m0.191s
sys 0m0.004s
你可能意味着它节省空间。但是,这是基于用例的选择,似乎编译器设计者更喜欢速度。
这是代码(我跟踪也相等)
struct Counter
{
static int count_ctor;
static int count_copy;
static int count_equal;
Counter(int){count_ctor++;}
Counter(const Counter&){count_copy++;}
Counter(Counter&&) noexcept{}
Counter & operator=(Counter const &){ count_equal++ ;}
};
int Counter::count_copy = 0;
int Counter::count_ctor = 0;
int Counter::count_equal = 0;
int main(void)
{
int size(100000005);
#ifdef EMPLACE
std::vector<Counter> v;
v.reserve(size);
for(int i = size; i>0 ; --i){ v.emplace_back(0);}
#else
std::vector<Counter> v(size,0);
#endif
std::printf("Copies: %d , Construct: %d, Equal copies %d",Counter::count_copy, Counter::count_ctor, Counter::count_equal);
return 0;
}
使用g ++ -DEMPLACE --std = c ++ 11 -O3 或不使用EMPLACE进行编译,以获得所需的二进制文件。
第二次测试
为了驳斥OP所做的假设,我们进行了以下测试:
我们用
制作了两个二进制文件g++ -DEMPLACE --std=c++11 -O3 copyc.cpp -o copyc && g++ --std=c++11 -O3 copyc.cpp -o copyc_copy
并且为了避免两者中的任何一个从操作系统中获得优先处理,我们在它们之间设置了10秒的标准暂停,并且我们在系统处于空闲状态时启动。
示例性运行如下。
export K=10192 ; time ./copyc_copy $K ; sleep 10; time ./copyc $K
Copies: 10192 , Construct: 1, Equal copies 0
real 0m2.888s
user 0m0.666s
sys 0m2.219s
Copies: 0 , Construct: 10192, Equal copies 0
real 0m3.376s
user 0m1.105s
sys 0m2.270s
我在多种情况下运行它,也在反向运行
Copies: 0 , Construct: 10192, Equal copies 0
real 0m3.154s
user 0m0.886s
sys 0m2.267s
Copies: 10192 , Construct: 1, Equal copies 0
real 0m2.573s
user 0m0.531s
sys 0m2.025s
据说这是一个不切实际的测试,但是花了这个匹配时间,我打赌编译器设计者做了更多,所有从gnu到clang和VS决定实现构造复制策略。我确信他们也有其他原因。
第二次测试的代码如下:
#include <vector>
#include <iostream>
#include <cstdlib>
template<typename T> static std::vector<T> get5()
{
std::vector<T> s;
s.reserve(5);
for(int i=5; i!=0 ;--i)
{
s.emplace_back(T());
}
return s;
}
struct test_struct
{
volatile int internals[255];
};
struct test_create
{
std::vector<test_struct> s;
test_create() : s(5){}
};
struct test_emplace
{
std::vector<test_struct> s;
test_emplace() : s(get5<test_struct>()){}
};
struct Counter
{
static int count_ctor;
static int count_copy;
static int count_equal;
#ifdef EMPLACE
test_emplace t[100];
#else
test_create t[100];
#endif
Counter(int)
{
count_ctor++;
}
Counter(const Counter&)
{
count_copy++;
}
Counter(Counter&&) noexcept
{
}
Counter & operator=(Counter const &){ count_equal++ ;}
};
int Counter::count_copy = 0;
int Counter::count_ctor = 0;
int Counter::count_equal = 0;
int main(int arg, char const * argv[])
{
int size(std::atoi(argv[1]));
#ifdef EMPLACE
std::vector<Counter> v;
v.reserve(size);
for(int i = size; i>0 ; --i)
{
v.emplace_back(0);
}
#else
std::vector<Counter> v(size,0);
#endif
std::printf("Copies: %d , Construct: %d, Equal copies %d",Counter::count_copy, Counter::count_ctor, Counter::count_equal);
return 0;
}
答案 1 :(得分:1)
您可以简单地定义一个以您想要的方式创建矢量的函数。
作为一个函数,初始化代码是异常安全的。
#include <iostream>
#include <vector>
#include <stddef.h> // ptrdiff_t
#include <utility> // std::forward
using namespace std;
struct Counter
{
static int n_constructor_calls;
Counter( int )
{
++n_constructor_calls;
}
Counter( Counter const& )
{
++n_constructor_calls;
}
Counter( Counter&& ) noexcept
{
++n_constructor_calls;
}
};
int Counter::n_constructor_calls = 0;
//--------------------------------------
using Size = ptrdiff_t;
using Index = Size;
template< class Item, class... Args >
auto make_vector( Size const n, Args&&... args )
-> vector<Item>
{
vector<Item> result;
result.reserve( n );
for( Index i = 0; i < n; ++i )
{
result.emplace_back( forward<Args>( args )... );
}
return result;
}
auto main() -> int
{
auto vec = make_vector<Counter>( 5, 42 );
cout << Counter::n_constructor_calls << " constructor calls.\n";
}
(输出“5个构造函数调用。”)
你基本上问,为什么不定义vector
构造函数来执行此操作,
“为什么编译器不会从构造函数参数中置位对象,或者将临时对象移动到第一个位置然后从中复制其余对象?
一个原因是这个构造函数是在C ++ 11中引入移动语义之前定义的。
相对于现有的C ++代码库来说,引入其他构造函数(更改过载行为)或更改现有构造函数的行为是非常昂贵的。