0. 序
今天写一道OJ的时候,发现死活超时,后来对比答案发现是因为我用了初始化列表导致超时了。因此写一篇博客记录一下。
1. 花括号统一初始化
一个容易混淆的概念,花括号初始化与initializer_list
类。在effective modern C++中item 7详细阐述了这一点。之前一个郁闷的报错是
Widget w();
Widget w{};
希望初始化一个没有参数的构造函数时,第一种初始化方法会报错,因为gcc会认为这是在声明一个函数。
通常在花括号初始化对象时,并不代表一定会产生相应的initializer_list
实例。仅仅是调用相应的构造函数。但如果有相应的以initializer_list
为参数的构造函数时,编译器会偏向于选择该构造函数。
class Widget{
public:
Widget(int i, double b);
Widget(std::initializer_list<double> a);
};
Widget w{10, 3.5}; // 调用第二个构造函数
Widget w(10, 3.5) // 调用第一个构造函数
2. initializer_list的开销
首先要回答的一个问题是,initializer_list
到底是一个什么样的类。可以从windows下的实现看到
template <class _Elem>
class initializer_list {
public:
using value_type = _Elem;
using reference = const _Elem&;
using const_reference = const _Elem&;
using size_type = size_t;
using iterator = const _Elem*;
using const_iterator = const _Elem*;
constexpr initializer_list() noexcept : _First(nullptr), _Last(nullptr) {}
constexpr initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) noexcept
: _First(_First_arg), _Last(_Last_arg) {}
_NODISCARD constexpr const _Elem* begin() const noexcept {
return _First;
}
_NODISCARD constexpr const _Elem* end() const noexcept {
return _Last;
}
_NODISCARD constexpr size_t size() const noexcept {
return static_cast<size_t>(_Last - _First);
}
private:
const _Elem* _First;
const _Elem* _Last;
};
可以看到,其实initializer_list
有两个指针,分别指向数据的开头和结尾,这意味着initializer_list
指向的数据是连续存放的,但是我们使用时并传入的参数通常不会有着连续的地址。这就需要编译器就帮助了。
vector<int> a{1,2,3};
//等价于下面的语句
const int tmp[3] = {1, 2, 3};
vector<int> a(initializer_list{tmp, tmp + 3});
也就是说,编译器在编译时会增加显式分配一段连续空间的语句,并且以copy的形式,把数据传入这段地址。
一篇值得参考的文章:The cost of std::initializer_list
因此下面的两条语句,前者的执行速度会比后者慢得多(所以导致OJ超时了)。
max({a, b, c});
max(a, max(b, c));