Предположим, я хочу написать что-то такое, что:
- возвращает объект
- при определенных обстоятельствах, в зависимости от параметра функции, объект имеет фиксированное значение, которое можно рассчитать только один раз, чтобы сэкономить время. Поэтому естественный выбор - сделать этот объект
static
. - в противном случае функция должна генерировать объект на лету
Что лучше написать эту функцию, с требованиями, которые:
- при вызове не требуется никаких конструкторов копирования, только перемещение, чтобы предотвратить копирование всех данных дорогого объекта
- Я не хочу использовать
new
сырые указатели. Если требуются указатели, они должны быть умными и автоматически удалять
Вариант использования следующий:
- определенные значения очень распространены, поэтому я хочу их кешировать
- другие значения очень редки, поэтому я хочу, чтобы они не кэшировались
Вызывающая сторона не должна знать, какие значения являются общими, интерфейс должен быть прозрачным для обоих случаев.
Пока что единственной чистой реализацией, которой я управлял, является использование shared_ptr
как показано ниже, но это похоже на излишество. В частности, потому что это делает распределение кучи, где он чувствует, что он действительно не требуется. Есть ли лучший подход?
#include <cassert>
#include <iostream>
#include <memory>
struct C {
int i;
static int count;
C(int i) : i(i) {
std::cout << "constr" << std::endl;
count++;
}
C(const C& c) : C(c.i) {
std::cout << "copy" << std::endl;
}
~C() {
std::cout << "destr" << std::endl;
count--;
}
};
int C::count = 0;
std::shared_ptr<C> func_reg_maybe_static(int i) {
static auto static_obj = std::make_shared<C>(0);
if (i == 0) {
return static_obj;
} else {
return std::make_shared<C>(i);
}
}
int main() {
assert(C::count == 0);
{
auto c(func_reg_maybe_static(0));
assert(c->i == 0);
assert(C::count == 1);
}
assert(C::count == 1);
{
auto c(func_reg_maybe_static(0));
assert(c->i == 0);
assert(C::count == 1);
}
assert(C::count == 1);
{
auto c(func_reg_maybe_static(1));
assert(c->i == 1);
assert(C::count == 2);
}
assert(C::count == 1);
{
auto c(func_reg_maybe_static(2));
assert(c->i == 2);
assert(C::count == 2);
}
assert(C::count == 1);
}
Который я компилирую с GCC 6.4.0:
g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main.out func_ret_maybe_static.cpp
и это производит ожидаемый вывод (гарантированный копией elision, я полагаю):
constr
constr
destr
constr
destr
destr
Я добавил статический счетчик ссылок C::count
просто для проверки того, что объекты действительно удаляются, как и ожидалось.
Если бы у меня не было статического случая, я бы просто сделал прямо:
C func_reg_maybe_static(int i) {
return C(i);
}
и копирование семантики elision/move сделает все эффективным.
Однако, если я попробую что-то аналогичное, как в:
C func_reg_maybe_static(int i) {
static C c(0);
return c;
}
затем C++ ловко прекращает просто перемещать C и начинает копировать его, чтобы избежать повреждения static
.