Thư viện STL cung cấp một tập hợp các hàm toàn cục trong tệp <bits/stl_uninitialized.h> để hỗ trợ khởi tạo bộ nhớ cho các container. Bài viết này phân tích chi tiết cơ chế hoạt động của các hàm này.
Hàm __uninitialized_default_n_a
Hàm này khởi tạo một dải phần tử bằng constructor mặc định.
template<typename Iter, typename Size, typename Allocator>
Iter __uninitialized_default_n_a(Iter start, Size count, Allocator& alloc) {
Iter current = start;
try {
for (; count > 0; --count, ++current) {
std::allocator_traits<Allocator>::construct(alloc, &*current);
}
return current;
} catch(...) {
std::_Destroy(start, current, alloc);
throw;
}
}
Phiên bản chuyên biệt cho bộ cấp phát std::allocator:
template<typename Iter, typename Size, typename T>
Iter __uninitialized_default_n_a(Iter start, Size count, std::allocator<T>&) {
return std::__uninitialized_default_n(start, count);
}
Hàm __uninitialized_default_n xác định chiến lược khởi tạo dựa trên tính chất của kiểu dữ liệu:
template<typename Iter, typename Size>
Iter __uninitialized_default_n(Iter start, Size count) {
using ValueType = typename std::iterator_traits<Iter>::value_type;
bool can_fill = std::is_integral<Size>::value && std::is_copy_assignable<ValueType>::value;
return __dispatch_default_n<std::is_trivial<ValueType>::value && can_fill>::execute(start, count);
}
Chiến lược khởi tạo được chia thành hai trường hợp:
template<bool IsTrivial>
struct __dispatch_default_n {
template<typename Iter, typename Size>
static Iter execute(Iter start, Size count) {
Iter cur = start;
try {
for (; count > 0; --count, ++cur) {
std::_Construct(&*cur);
}
return cur;
} catch(...) {
std::_Destroy(start, cur);
throw;
}
}
};
template<>
struct __dispatch_default_n<true> {
template<typename Iter, typename Size>
static Iter execute(Iter start, Size count) {
if (count > 0) {
auto* first_elem = &*start;
std::_Construct(first_elem);
++start;
start = std::fill_n(start, count - 1, *first_elem);
}
return start;
}
};
Hàm __uninitialized_fill_n_a
Hàm này khởi tạo dải phần tử bằng cách sao chép từ một giá trị cho trước.
template<typename Iter, typename Size, typename T, typename Allocator>
Iter __uninitialized_fill_n_a(Iter start, Size count, const T& val, Allocator& alloc) {
Iter cur = start;
try {
for (; count > 0; --count, ++cur) {
std::allocator_traits<Allocator>::construct(alloc, &*cur, val);
}
return cur;
} catch(...) {
std::_Destroy(start, cur, alloc);
throw;
}
}
Logic lựa chọn giữa constructor và phép gán:
template<typename Iter, typename Size, typename T>
Iter uninitialized_fill_n(Iter start, Size count, const T& val) {
using ValueType = typename std::iterator_traits<Iter>::value_type;
bool use_assign = __use_assign_for_init<ValueType, const T&>() && std::is_integral<Size>::value;
return __dispatch_fill_n<use_assign>::execute(start, count, val);
}
Hàm __uninitialized_copy_a
Hàm này sao chép một dải phần tử sang vùng nhớ chưa được khởi tạo.
template<typename InputIter, typename OutputIter, typename Allocator>
OutputIter __uninitialized_copy_a(InputIter src_begin, InputIter src_end, OutputIter dest, Allocator& alloc) {
OutputIter cur = dest;
try {
for (; src_begin != src_end; ++src_begin, ++cur) {
std::allocator_traits<Allocator>::construct(alloc, &*cur, *src_begin);
}
return cur;
} catch(...) {
std::_Destroy(dest, cur, alloc);
throw;
}
}
Quyết định sử dụng memmove hoặc phép gán:
template<typename InputIter, typename OutputIter>
OutputIter uninitialized_copy(InputIter src_begin, InputIter src_end, OutputIter dest) {
using SrcType = typename std::iterator_traits<InputIter>::value_type;
using DstType = typename std::iterator_traits<OutputIter>::value_type;
bool use_memmove = std::is_trivial<SrcType>::value;
bool assignable = __use_assign_for_init<DstType, decltype(*src_begin)>();
return __dispatch_copy<use_memmove && assignable>::execute(src_begin, src_end, dest);
}
Hàm __uninitialized_move_a
Hàm này thực hiện di chuyển (move) hoặc sao chép các phần tử.
template<typename InputIter, typename OutputIter, typename Allocator>
OutputIter __uninitialized_move_a(InputIter src_begin, InputIter src_end, OutputIter dest, Allocator& alloc) {
return __uninitialized_copy_a(std::make_move_iterator(src_begin),
std::make_move_iterator(src_end),
dest, alloc);
}
Tổng Kết Chiến Lược Khởi Tạo
- __uninitialized_default_n_a: Sử dụng constructor mặc định. Với kiểu dữ liệu tầm thường (trivial) và có thể gán, tối ưu bằng phép gán hoặc
memset. - __uninitialized_fill_n_a: Sử dụng constructor sao chép. Tối ưu tương tự với kiểu dữ liệu phù hợp.
- __uninitialized_copy_a: Sao chép giữa hai dải. Tối ưu bằng
memmovenếu kiểu dữ liệu cho phép, ngược lại dùng constructor sao chép. - __uninitialized_move_a: Tương tự sao chép nhưng hỗ trợ ngữ nghĩa di chuyển trong C++11 trở lên.
Các hàm này đều cung cấp cơ chế an toàn ngoại lệ (exception safety), hủy các đối tượng đã được khởi tạo khi xảy ra lỗi.