C++ динамический доступ к членам переменным

Вопрос или проблема

На данный момент у меня есть конструкция switch, которая принимает ввод и выбирает одно из следующих действий:

Опция 1

for(; i < queue_size; ++i)
{
   prepared->setString(i+1, queue.at(i).model);
}

Опция 2

for(; i < queue_size; ++i)
{
   prepared->setString(i+1, queue.at(i).manufacturer);
}

Опция 3

for(; i < queue_size; ++i)
{
   prepared->setString(i+1, queue.at(i).name);
}

В PHP вы могли бы сделать то же самое, сделав что-то вроде этого:

$queue[i][$member];

$member затем можно установить на “name”, “manufacturer” и т.д.

Существует ли способ сделать что-то подобное или более надежное в C++?

Заранее спасибо за любую помощь/предложения!

Используя C++11 std::function или boost::function, если у вас нет C++11:

std::map<YourSwitchType, std::function<void(void)> functionMap;

Затем определите функции, такие как

void manufacturString() {
  for(; i < queue_size; ++i) {
    prepared->setString(i+1, queue.at(i).manufacturer);
  }
}

для каждого случая и заполните карту этими функциями.

functionMap[someSwitchValue] = std::bind(&ThisType::manufactureString, this);

Затем вы можете просто вызвать их:

functionMap[someSwitchValue]();

Одно из преимуществ этого подхода заключается в том, что он не ограничивает вас только членами класса. Вы можете помещать функции, функторы, статические члены и не-члены в одну и ту же карту. Единственное ограничение заключается в том, что после связывания они возвращают void и не принимают аргументов (это специфично для этого примера).

Вы можете сделать это с помощью map от ваших названий свойств к указателю на член. Но это немного работает (вам нужно создать это сопоставление самостоятельно), и синтаксис становится немного сложным. (И все члены, которые вы хотите адресовать таким образом, должны быть одного типа.)

Демо:

#include <iostream>
#include <map>

struct Foo {
    std::string name;
    std::string address;
};

typedef std::string Foo::* FooMemPtr;
typedef std::map<std::string, FooMemPtr> propmap;

int main()
{
    propmap props;
    props["name"] = &Foo::name;
    props["address"] = &Foo::address;

    /* ... */

    Foo myfoo;
    myfoo.*(props["name"]) = "myname";
    myfoo.*(props["address"]) = "myaddress";
    std::cout << myfoo.*(props["address"]) << std::endl;
    std::cout << myfoo.*(props["name"]) << std::endl;
}

Если вы можете использовать перечисления вместо строк, то вы можете получить доступ к имени, производителю и т.д. с индексом от значений перечисления. Это зависит от того, насколько динамичным вам нужно быть.

Используйте STL карту для выполнения этого. Это работает так же, как вы делали бы в PHP.

Одним из вариантов является передача функторов извлечения в функцию:

#include <string>
#include <vector>
#include <boost/bind.hpp>

struct Some {
    std::string model, manufacturer, name;
};

struct Prepared {
    void setString(size_t, std::string const&);
};

template<class Extractor>
void foo(Extractor extract) {
    Prepared* prepared = 0;
    std::vector<Some> queue;
    size_t i, queue_size = queue.size();
    for(; i < queue_size; ++i) {
        prepared->setString(i+1, extract(queue.at(i)));
    }
}

int main() {
    // C++03
    foo(boost::bind(&Some::model, _1));
    foo(boost::bind(&Some::manufacturer, _1));
    foo(boost::bind(&Some::name, _1));

    // C++11
    foo([](Some const& some){ return some.model; });
    foo([](Some const& some){ return some.manufacturer; });
    foo([](Some const& some){ return some.name; });
}

Вы можете делать подобные вещи, используя указатели на член переменной (также есть указатели на функцию члена). Функции привязки Boost являются более универсальными, но на самом деле это то, что они делают под капотом (по крайней мере в этом сценарии).

#include <iostream>
#include <string>

struct item
{
    std::string a, b;
};

//реально ужасный синтаксис для указателя на член-переменную
typedef std::string item::*mem_str;

//показать каждую переменную из списка
void show_test( item * objs, size_t len, mem_str var )
{
    for( size_t i=0; i < len; ++i )
        std::cout << objs[i].*var << std::endl;
}

int main()
{
    //создание тестовых данных
    item test[2];
    test[0].a = "A-Zero";
    test[0].b = "B-Zero";

    test[1].a = "A-One";
    test[1].b = "B-One";

    //показать переменные
    show_test( test, 2, &item::a );
    show_test( test, 2, &item::b );

    return 0;
}

Ответ или решение

Динамическое обращение к членам классов в C++

В C++ нет прямого аналога динамического обращения к членам переменным, как в PHP, где вы можете использовать синтаксис $queue[i][$member]. Однако есть несколько методов, которые позволяют достичь желаемой функциональности, хотя и с использованием других конструкций. Давайте рассмотрим несколько подходов, которые помогут вам реализовать такую функциональность.

1. Использование указателей на члены классов

Одним из наиболее мощных и гибких способов является использование указателей на члены классов. Это позволяет вам динамически получать доступ к переменным экземпляров классов, используя мапу, где ключами будут строки, а значениями — указатели на соответствующие члены.

Пример:
#include <iostream>
#include <map>
#include <string>

struct Item {
    std::string name;
    std::string manufacturer;
};

typedef std::string Item::* ItemMemPtr;
typedef std::map<std::string, ItemMemPtr> PropertyMap;

int main() {
    PropertyMap props;
    props["name"] = &Item::name;
    props["manufacturer"] = &Item::manufacturer;

    Item item;
    item.*(props["name"]) = "MyItem";
    item.*(props["manufacturer"]) = "MyManufacturer";

    std::cout << "Item Name: " << item.*(props["name"]) << std::endl;
    std::cout << "Item Manufacturer: " << item.*(props["manufacturer"]) << std::endl;

    return 0;
}

В этом примере мы создаем мапу, которая сопоставляет строки с указателями на члены класса Item. Это позволяет нам просто передавать строки и динамически получать значения из объекта item.

2. Использование std::function и std::bind

Если вам необходимо выполнять не только динамический доступ к переменным, но и вызывать соответствующие функции или изменять поведение в зависимости от типа данных, вы можете использовать std::function и std::bind. Это также позволяет уменьшить количество кода и повысить его читаемость.

Пример:
#include <iostream>
#include <functional>
#include <map>
#include <vector>
#include <string>

struct Item {
    std::string model;
    std::string manufacturer;
    std::string name;
};

struct Prepared {
    void setString(size_t index, const std::string& value) {
        std::cout << "Set String at index " << index << ": " << value << std::endl;
    }
};

void processItems(Prepared* prepared, const std::vector<Item>& queue, std::function<std::string(const Item&)> extractor) {
    for (size_t i = 0; i < queue.size(); ++i) {
        prepared->setString(i + 1, extractor(queue[i]));
    }
}

int main() {
    Prepared prepared;
    std::vector<Item> items = { {"ModelA", "ManufacturerA", "NameA"}, {"ModelB", "ManufacturerB", "NameB"} };

    processItems(&prepared, items, [](const Item& item) { return item.model; });
    processItems(&prepared, items, [](const Item& item) { return item.manufacturer; });
    processItems(&prepared, items, [](const Item& item) { return item.name; });

    return 0;
}

Здесь мы используем лямбда-функции, чтобы передать разные способы извлечения данных из объектов Item. Это решение более универсально и может быть легко адаптировано под разные сценарии.

3. Использование std::map и std::function

Этот метод позволит объединить все в одну мапу, где вы можете хранить как функции, так и указатели на члены.

Пример:
#include <iostream>
#include <map>
#include <functional>
#include <string>

struct Item {
    std::string name;
    std::string manufacturer;
};

typedef void (Item::*ItemFuncPtr)(const std::string&);

class ItemAccessor {
public:
    void setName(const std::string& value) { name = value; }
    void setManufacturer(const std::string& value) { manufacturer = value; }

private:
    std::string name;
    std::string manufacturer;
};

int main() {
    std::map<std::string, ItemFuncPtr> funcMap;
    funcMap["name"] = &Item::setName;
    funcMap["manufacturer"] = &Item::setManufacturer;

    Item item;
    (item.*funcMap["name"])("MyItem");
    (item.*funcMap["manufacturer"])("MyManufacturer");

    return 0;
}

Эти подходы обеспечивают гибкость и динамичность в доступе к членам классов, что делает C++ более пригодным для реализации шаблонов, схожих с PHP, но с использованием строгих типов и компиляции времени. Выбор подхода будет зависеть от контекста вашей задачи и требований к производительности.

Оцените материал
Добавить комментарий

Капча загружается...