Comments 27
UFO just landed and posted this here
Как я понимаю, всё это уже есть в Boost.TypeErasure. Об использовании подробно рассказывалось в «Andrzej's C++ blog».
Похоже, но здесь нет ни одного шаблона снаружи (в отличие от type_erasure), а в статье как раз целый раздел посвящён тому, за что можно не любить шаблоны. Подход в статье также не призывает делать лишние cast'ы. К тому же можно свободно использовать наследники, не заморачиваясь только на object, как в Boost на any.
В самой статье шаблоны часто используются. Касты, наследники и общие типы не обязательны. Тем более, что с type_erasure получается значительно короче и лаконичнее:
BOOST_TYPE_ERASURE_MEMBER((has_name), Name, 0)
BOOST_TYPE_ERASURE_MEMBER((has_price), Price, 0)
namespace te = boost::type_erasure;
using Goods = boost::mpl::vector<te::copy_constructible<>, has_name<std::string(), te::_self>,
has_price<float(), te::_self>, te::relaxed>;
using AnyGoods = te::any<Goods>;
// Протестируем
class Candies {
public:
Candies(std::string const &mName, float mPrice) : m_name(mName), m_price(mPrice) {}
std::string Name() const { return m_name; }
float Price() const { return m_price; }
private:
std::string m_name;
float m_price;
};
class EmptyGoods {
public:
std::string Name() const { return "empty"; }
float Price() const { return 0; }
};
TEST(AnyGoods_Should, contain_various_types) {
AnyGoods goods1 = Candies("test", 123);
AnyGoods goods2 = EmptyGoods();
EXPECT_THAT(goods1.Name(), Eq("test"));
EXPECT_THAT(goods1.Price(), Eq(123));
EXPECT_THAT(goods2.Name(), Eq("empty"));
EXPECT_THAT(goods2.Price(), Eq(0));
}
Ну если тебе нравится подход с вектором из кучи элементов в виде mpl-каши, то тоже вариант. Но вообще мой подход даёт возможность писать классы, просто описывая классы как обычно, просто они становятся юзабельными по значению, независимо от размера, прячут данные в реализацию и могут быть контейнерами для наследников. А так конечно можно и на void* всё то же самое сделать.
Я не понимаю зачем это делать для SQL.
Вот пример использования libpqxx
Вот пример использования libpqxx
const std::string query = "SELECT id, type_id, measurement_id FROM qoscfg.kpi";
pqxx::work work(*m_connection, "GetKpii");
const auto res = work.exec(query);
for (auto i = res.begin(), r_end =res.end(); i != r_end; ++i)
{
size_t id = 0;
(*i)[0].to(id);
std::string type;
(*i)[1].to(type);
size_t mid = 0;
(*i)[2].to(mid);
const auto kpi = std::make_shared<Kpi>(id, type, m_measurement[mid]);
list.push_back(kpi);
}
Затем, что в большинстве случаев тебе надо запаковывать результат запроса в компактный набор значений, неопределённого на этапе компиляции типа, причём память, желательно, выделить однажды, а значениями всё забить так, чтобы с набором было удобно работать.
А это как вы системы запроектируете. Я проектирую слой работы с БД так, что бы таких случаев не было, у меня всегда типы известны.
Если делать SQL-конструктор на основе конструкций C++ или просто даже библиотеку общего пользования, то на этапе компиляции знать типы того, что придёт из БД не известно. То же касается и RPC.
У базы данных есть схема, в которой четко прописаны типы. Я привел пример с работой PostgreSQL, там в протоколе содержится информация о типах. Я разрабатываю базу в терминах предметной области и мне удобно опрерировать простым набором объектов, которые соответствуют таблицам.
В случае RPC должна быть некая конвенция о формате и типах данных. Например в случае с JSON-RPC я использую JSON схему для валидации.
SQL-конструкторы появляются в разработке своей ORM, или разработка всяческих конструкторов схем данных. Там действительно схема данных определяется пользователем, а не программистом.
В случае RPC должна быть некая конвенция о формате и типах данных. Например в случае с JSON-RPC я использую JSON схему для валидации.
SQL-конструкторы появляются в разработке своей ORM, или разработка всяческих конструкторов схем данных. Там действительно схема данных определяется пользователем, а не программистом.
Пользователь ORM-системы или SQL-генератора, наподобие LINQ является точно такой же программист, которому нужен удобный API, эффективность при выполнении инструкций и интуитивно понятный код в результате разработки. Разработчик библиотеки в свою очередь разумеется не знает заранее о том, что за типы придут с некой заранее неизвестной базы данных или с удалённого клиента RPC-протокола.
очередь разумеется не знает
А вот тут вы лукавите. Кое что разработчик API сделать может. Например разработчики libpqxx сделали такой метод, для извлечения результата:
template<typename T > bool pqxx::field::to (T & Obj) const
Отсюда имеем ситуацию:
- я как пользователь знаю какие типы должен возвращать запрос;
- разработчики API ввели ограничения и сказали явно указывайте тип;
- во время выполнения библиотека знает какие типы пришли в запросе, какие типы указал пользователь и делает преобразование типов проверив на возможность такого действия.
и все это без таких сложностей, какие привели вы.
Неправда Ваша, смотрите, то что я предлагаю — это по сути аналог pqxx::field, только в более широком спектре использования. Никто не мешает сделать так:
template <typename value_type> value_type object::to() const;
Скажу больше, ровно так и нужно сделать. Обобщённый тип всегда должен давать возможность работать с содержимым в виде нативного значения.
template <typename value_type> value_type object::to() const;
Скажу больше, ровно так и нужно сделать. Обобщённый тип всегда должен давать возможность работать с содержимым в виде нативного значения.
К разговору о динамической типизации. Если мне нужно передать некоторое значение, которое будет приводиться к конкретному типу, основываясь на внутреннем состоянии, то как мне указать в функции, что аргумент не имеет определенного типа?
Вариант использовать разные функции не подходит, так как принцип работы сложнее, чем в примере. Конечно, можно использовать структуру в которую и поместить data, но может есть более лаконичное решение.
void SomeClass::DoSomething(??? * data) {
switch(this->state) {
case 1:
int * value = reinterpret_cast<int>(data);
// do something with value
break;
case 2:
std::string * value = reinterpret_cast<std::string>(data);
// do something with value
break;
}
}
Вариант использовать разные функции не подходит, так как принцип работы сложнее, чем в примере. Конечно, можно использовать структуру в которую и поместить data, но может есть более лаконичное решение.
Описанное, больше похоже на реализацию pimpl. Ну, и на динамическую типизацию не особо тянет пока что.
Кстати, у Майерса в его книженции про 11ый есть ряд рекомендаций по типовой реализации pimpl, не совсеми я согласен, но почитать стоит.
Кстати, у Майерса в его книженции про 11ый есть ряд рекомендаций по типовой реализации pimpl, не совсеми я согласен, но почитать стоит.
Это не pimpl, я понимаю откуда такая аналогия. По сути это инкапсуляция интерфейса и подразумевает наследование. Pimpl — по сути просто класс с реализацией, с прокси-декоратором снаружи. У одного класса, в подходе Майерса, есть как правило класс двойник, и инкапсулировать прокси-класс с API может только его. Здесь подход именно на том, что наследование внутренних классов с данными инкапсулировано. Мы запросто можем положить в object любой его наследник.
Есть минус — невозможны взаимные ссылки по таким прокси-объектам.
class A { B b; }; class B { A a; };
По простым (или умным) указателям-то с forward-декларацией разруливается.
Из плюсов — можно операторы правильно перегружать.
class A { B b; }; class B { A a; };
По простым (или умным) указателям-то с forward-декларацией разруливается.
Из плюсов — можно операторы правильно перегружать.
UFO just landed and posted this here
Ну сам-то object с данными по умолчанию вполне себе всегда is_null(), а вот его наследники null далеко не всегда.
UFO just landed and posted this here
Тоже вариант, но так нагляднее, не приходится везде во всех методах писать проверку на nullptr для m_data. Впрочем никто не запрещает создать шаблон от типа данных, наподобие nullable<typename data_type> и перегрузить там operator -> для проверки на nullptr указателя на data_type. Но опять же, теряется наглядность учебного материала.
Sign up to leave a comment.
Побег из темницы типов. Реализуем работу с данными, тип которых определяется динамически