ТЛ; др: Есть ли способ добавить аргумент по умолчанию из текущей области видимости для всех неявных конструкторов в C ++?
В настоящее время я разрабатываю интерфейс для встроенного языка в C ++. Цель состоит в том, чтобы сделать синтаксически правильные выражения максимально безопасными и удобными. Прямо сейчас, я думаю, что изучение тяжеловесной реализации, такой как boost :: proto, приведет к слишком большой задержке в разработке, поэтому я попытаюсь развернуть свою собственную реализацию.
Вот небольшая демонстрация:
#include <iostream>
#include <string>
#include <sstream>
class ExprBuilder
{
public:
ExprBuilder(const int val) : val(std::to_string(val)) {}
ExprBuilder(const std::string val) : val(val) {}
ExprBuilder(const char* val) : val(val) {}
ExprBuilder(const ExprBuilder& lhs, const ExprBuilder& arg) {
std::stringstream ss;
ss << "(" << lhs.val << " " << arg.val << ")";
val = ss.str();
}
const ExprBuilder operator()(const ExprBuilder& l) const {
return ExprBuilder(*this, l);
}
template<typename... Args>
const ExprBuilder operator()(const ExprBuilder& arg, Args... args) const
{
return (*this)(arg)(args...) ;
}
std::string val;
};
std::ostream& operator<<(std::ostream& os, const ExprBuilder& e)
{
os << e.val;
return os;
}
int main() {
ExprBuilder f("f");
std::cout << f(23, "foo", "baz") << std::endl;
}
Как видите, встраивать выражения довольно просто из-за перегрузки C ++ и неявных преобразований.
Однако я сталкиваюсь с практической проблемой: в приведенном выше примере все данные были размещены в виде объектов std :: string. На практике мне нужно нечто более сложное (узлы AST), которые размещаются в куче и управляются выделенным владельцем (устаревший код не может быть изменен). Поэтому я должен передать уникальный аргумент (указанный владелец) и использовать его для распределения. Я бы предпочел не использовать статическое поле здесь.
То, что я ищу, — это способ использования запроса пользователя о предоставлении такого владельца при каждом использовании компоновщика, но удобным способом. Что-то вроде динамически изменяемой переменной было бы здорово. Есть ли способ получить следующее в C ++:
class ExprBuilder
{
...
ExprBuilder(const ExprBuilder& lhs, const ExprBuilder& arg) {
return ExprBuilder(owner.allocate(lhs, rhs)); // use the owner implicitly
}
...
};
int main() {
Owner owner; // used in all ExprBuilder instances in the current scope
ExprBuilder f("f");
std::cout << f(23, "foo", "baz") << std::endl;
}
Это возможно?
редактировать: Я хотел бы уточнить, почему я (до сих пор) не рассматривал глобальную переменную. Владелец должен быть вручную освобожден пользователем разработчика в какой-то момент, поэтому я не могу создать одно специальное. Следовательно, пользователь может вообще «забыть» владельца. Чтобы избежать этого, я ищу способ обеспечить присутствие владельца проверкой типов.
Это вряд ли возможно без глобальных / статических переменных, потому что без глобальной / статической информации локальные переменные Owner owner
а также ExprBuilder f
не может ничего знать друг о друге.
Я думаю, что самый чистый способ — это добавить
static Owner* current_owner;
к ExprBuilder
учебный класс. Затем вы можете добавить новый класс ScopedCurrentOwnerLock
, который устанавливает current_owner
в конструкторе и устанавливает его в nullptr в деструкторе. Тогда вы можете использовать его как блокировку мьютекса:
class ScopedCurrentOwnerLock {
public:
ScopedCurrentOwnerLock(Owner const& owner) {
ExprBuilder::current_owner = &owner;
}
~ScopedCurrentOwnerLock() {
ExprBuilder::current_owner = nullptr;
}
};
int main() {
Owner owner;
ScopedCurrentOwnerLock lock(owner);
ExprBuilder f("f");
}
Если у вас есть доступ к Owner
код, вы можете опустить ScopedCurrentOwnerLock
класс и непосредственно установить / снять указатель в конструкторе / деструкторе Owner
,
Обратите внимание на следующие две проблемы с этим решением:
Если владелец выходит из области действия до того, как блокировка выходит из области действия, у вас есть недопустимый указатель.
Статический указатель имеет непредсказуемое поведение, если у вас есть несколько блокировок одновременно, e. г. из-за многопоточности.
Все твои ExprBuilders
иметь зависимость от Owner
и вы по праву не хотите глобального государства. Таким образом, вы должны передать владельца каждому конструктору.
Если вы действительно не хотите добавлять owner,
для всех ваших экземпляров в блоке, вы можете создать фабрику, чтобы передать его для вас.
struct ExprBuilderFactory
{
Owner & owner;
ExprBuilder operator()(int val) { return ExprBuilder(owner, val); }
ExprBuilder operator()(char * val) { return ExprBuilder(owner, val); }
// etc
}
int main() {
Owner owner;
ExprBuilderFactory factory{ owner };
ExprBuilder f = factory("f");
}