Как член может знать, в каком экземпляре класса он построен? - PullRequest
0 голосов
/ 03 декабря 2008

    class C {
    public
      T x;
    };

Есть ли у конструктора x элегантный способ узнать неявно в каком экземпляре C он создает?


Я реализовал такое поведение с какой-то грязной и не элегантной машиной. Мне нужно это для моей оболочки sqlite3. Мне не нравятся все обертки, которые я видел, их API-интерфейс уродлив и неудобен. Я хочу что-то вроде этого:
<code>
    class TestRecordset: public Recordset {
    public:
      // The order of fields declarations specifies column index of the field.
      // There is TestRecordset* pointer inside Field class, 
      // but it goes here indirectly  so I don't have to 
      // re-type all the fields in the constructor initializer list.
      Field<__int64> field1;
      Field<wstring> field2;
      Field<double> field3;

      // have TestRecordset* pointer too so only name of parameter is specified
      // in TestRecordset constructor
      Param<wstring> param;

      virtual string get_sql() {
        return "SELECT 1, '1', NULL FROM test_table WHERE param=:PARAM";
      }

      // try & unlock are there because of my dirty tricks.
      // I want to get rid of them.

      TestRecordset(wstring param_value)
      try : Recordset(open_database(L"test.db")), param("PARAM") {
        param = param_value;
       // I LOVE RAII but i cant use it here. 
       // Lock is set in Recordset constructor, 
       // not in TestRecordset constructor.
        unlock(this);
        fetch();
      } catch(...) {
        unlock(this);
        throw;
      }
    };
<code>

Я хочу уточнить факт - это часть рабочего кода. Вы можете сделать это в C ++. Я просто хочу сделать это более приятным способом.


Я нашел способ избавиться от разблокировки и попробовать заблокировать. Я вспомнил, что существует такая вещь, как локальное хранилище потоков. Теперь я могу написать конструктор так просто:
  TestRecordset(wstring param_value): 
    Recordset(open_database(L"test.db")), param("PARAM") {
        param = param_value;
        fetch();
   }
</code>


к дрибам Моя цель - избежать лишнего и утомительного набора текста. Без некоторых уловок за сценой мне придется печатать для каждого поля и параметра:
TestRecordset(wstring param_value): Recordset(open_database(L"test.db")), param(this, "PARAM"),
   field1(this, 0), field2(this, 1), field3(this, 2) { ... }
</code>

Это избыточно, безобразно и неудобно. Например, если мне придется добавить новое поле в середина SELECT мне придется переписать все номера столбцов. Некоторые заметки к вашему посту:

  1. Поля и параметры инициализируются их конструкторами по умолчанию.
  2. Порядок инициализаторов в конструкторе не имеет значения. Поля всегда инициализируются в порядке их объявления. Я использовал этот факт для отслеживания индекса столбца для полей
  3. Базовые классы создаются первыми. Поэтому, когда поля построены, внутренний список полей в наборе записей готов к использованию конструктором по умолчанию Filed.
  4. Я не могу использовать RAII здесь. Мне нужно получить блокировку в конструкторе Recorset и снять ее обязательно в конструкторе TestRecordset после того, как все поля построены .

Ответы [ 5 ]

8 голосов
/ 03 декабря 2008

Нет. Объекты не должны знать, откуда они используются, чтобы работать. Что касается х, это экземпляр T. Вот и все. Он не ведет себя по-разному в зависимости от того, является ли он членом класса C, членом класса D, автоматическим, временным и т. Д.

Кроме того, даже если конструктор T знал об экземпляре C, этот экземпляр C был бы неполным, поскольку, конечно, он еще не закончил построение, поскольку его члены не были построены. C ++ предлагает вам много шансов выстрелить себе в ногу, но предложить вам ссылку на неполный объект в конструкторе другого класса не входит в их число.

Единственное, что я могу придумать, чтобы приблизить ваш пример кода, это сделать что-то вроде

#define INIT_FIELDS field1(this), field2(this), field3(this)

сразу после списка полей, затем используйте INIT_FIELDS в списке инициализатора и #undef его. Это все еще дублирование, но, по крайней мере, все это в одном месте. Однако это, вероятно, удивит ваших коллег.

Другой способ убедиться, что вы не забыли поле, - удалить конструктор с нулевым аргументом из поля. Опять же, вам все равно придется печатать, но, по крайней мере, если вы забудете что-то, компилятор поймает это. Я думаю, что не-СУХАЯ природа списков инициализаторов - это то, с чем C ++ просто должен жить.

2 голосов
/ 03 декабря 2008

Если добавить ответ «один за другим», то реальный вопрос, который вы должны задать, звучит так: «что плохого в моем проекте решения, когда объектам требуется знать, где они созданы?»

1 голос
/ 03 декабря 2008

Я так не думаю.

Из чистого любопытства, почему это должно иметь значение? у вас есть контекст, в котором это может быть полезно?

М.

0 голосов
/ 03 декабря 2008

Мне интересен ваш код. Вы прокомментируете, что все поля плюс атрибут param имеют указатели обратно в TestRecordSet, но их не нужно инициализировать? Или это вопрос вопроса, как избежать прохождения указателей this во время строительства?

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

Вы используете блок try constructor только для того, чтобы он рекомендовал использовать только эти функции (Всем, кто заинтересован, прочитайте GOTW # 66 ), если это действительно необходимо. Если член RecordSet был создан (и, таким образом, получена блокировка), а затем что-то пошло не так в конструкторе, тогда [см. Цитату ниже] RecordSet будет уничтожен, и если он использует RAII внутри, он освободит блокировку, поэтому я считаю, что try / catch может и не потребоваться.

C ++ 03, 15.2 Обработка исключений / Конструкторы и деструкторы

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

0 голосов
/ 03 декабря 2008

Я постоянно экспериментирую с такими вещами в C # - для этого я использую рефлексию.

Подумайте о том, чтобы получить библиотеку отражений или кода для C ++, которая поможет вам делать то, что вы хотите.

Теперь я не могу рассказать вам, как найти хорошую библиотеку для отражения или генерации кода для C ++, но это другой вопрос!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...