Я недавно узнал о std::move
а также rvalue
ссылки, и хочу знать, является ли мое использование их уместным и эффективным.
Считайте это простым Image
класс, который просто хранит массив unsigned char
значения, представляющие пиксели. (Обратите внимание, что на самом деле членов больше, чем одного массива символов, но здесь я упрощаю для удобства чтения.)
using PixelContainer = std::vector<unsigned char>;
class Image {
public:
Image(PixelContainer&& pixels) : m_pixels(std::move(pixels)) {};
Image& operator=(Image&& image) { m_pixels = std::move(image.m_pixels); return *this; };
private:
PixelContainer m_pixels;
};
А теперь рассмотрим этот статический метод ImageFactory
класс, который принимает имя файла .png
и возвращает Image
объект заполнен соответствующими значениями пикселей.
Image ImageFactory::loadImage(const char* filename) {
PixelContainer temp_pixels;
// ... fill pixels from file (details not relevant here)
Image temp_image(std::move(temp_pixels));
return temp_image;
}
Наконец, вместе они используются в коде как таковом:
Image image = ImageFactory::loadImage("image.png");
я использую rvalue
ссылки на предположительно избавляют от любого времени, потраченного на создание копий того, что потенциально может быть большим массивом значений пикселей. Поскольку созданный фабрикой вектор и изображение являются временными, их можно перемещать.
Мой вопрос: имеет ли смысл моя реализация, и если она не имеет существенных недостатков, можно ли ее еще улучшить?
Давайте разберемся! Я добавил такую функцию, чтобы она содержала ваш последний бит кода:
Image outer() {
Image image = ImageFactory::loadImage("image.png");
return image;
}
Мне также пришлось добавить обычный конструктор копирования для Image
(Я использовал = default
определить его), потому что, даже если он на самом деле не используется, он должен возвращаться по значению.
Затем я скомпилировал с помощью Clang -O2 -g -S и посмотрел на сборку. В основном это код для инициализации вектора: call operator new
и настройте внутренние указатели вектора. Там нет копирования, код выглядит достаточно чистым и эффективным. И, как вы можете надеяться, код для outer()
почти идентичен коду для loadImage()
(последний встраивается в первый, потому что я помещаю все в одну единицу перевода).
Для справки вот сборка, которую я получил:
outer(): ## @_Z5outerv
pushq %rbp
movq %rsp, %rbp
pushq %rbx
pushq %rax
movq %rdi, %rbx
movl $100000000, %edi ## imm = 0x5F5E100
callq operator new(unsigned long)
movd %rax, %xmm0
movlhps %xmm0, %xmm0 ## xmm0 = xmm0[0,0]
movq $-100000000, %rcx ## imm = 0xFFFFFFFFFA0A1F00
movq %rax, %rsi
LBB1_1: ## =>This Inner Loop Header: Depth=1
testq %rsi, %rsi
movl $0, %edx
je LBB1_3
movb $0, (%rsi)
movaps %xmm0, %xmm1
punpckhqdq %xmm1, %xmm1 ## xmm1 = xmm1[1,1]
movd %xmm1, %rdx
LBB1_3: ## %_ZNSt3__116allocator_traits...
incq %rdx
movd %rdx, %xmm1
punpcklqdq %xmm1, %xmm0 ## xmm0 = xmm0[0],xmm1[0]
incq %rcx
movq %rdx, %rsi
jne LBB1_1
## BB#4: ## %_ZN12ImageFactory9loadImageEPKc.exit
leaq 100000000(%rax), %rax
movdqu %xmm0, (%rbx)
movq %rax, 16(%rbx)
movq %rbx, %rax
addq $8, %rsp
popq %rbx
popq %rbp
retq