Слабосвязанное неявное преобразование - PullRequest
25 голосов
/ 15 января 2011

Неявное преобразование может быть действительно полезным, когда типы семантически эквивалентны. Например, представьте две библиотеки, которые реализуют тип одинаково, но в разных пространствах имен. Или просто тип, который в основном идентичен, за исключением некоторого семантического сахара здесь и там. Теперь вы не можете передать один тип в функцию (в одной из этих библиотек), которая была разработана для использования другого, если только эта функция не является шаблоном. Если это не так, вы должны каким-то образом преобразовать один тип в другой. Это должно быть тривиально (иначе все-таки типы не так идентичны!), Но при вызове преобразования явным образом происходит раздувание вашего кода в основном бессмысленными вызовами функций. Хотя такие функции преобразования могут на самом деле копировать некоторые значения, они, по сути, ничего не делают с точки зрения «программистов» высокого уровня.

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

Теперь я вижу два варианта, как заставить неявное преобразование работать в коде пользователя:

  1. Первый - предоставить прокси-тип, который реализует операторы преобразования и конструкторы преобразования (и присваивания) для всех задействованных типов и всегда использует это.

  2. Второе требует минимальных изменений в библиотеках, но обеспечивает большую гибкость: Добавьте конструктор преобразования для каждого задействованного типа, который может быть включен извне.

Например, для типа A добавить конструктор:

template <class T> A(
  const T& src,
  typename boost::enable_if<conversion_enabled<T,A>>::type* ignore=0
)
{
  *this = convert(src);
}

и шаблон

template <class X, class Y>
struct conversion_enabled : public boost::mpl::false_ {};

, который отключает неявное преобразование по умолчанию.

Затем, чтобы включить преобразование между двумя типами, специализируйте шаблон:

template <> struct conversion_enabled<OtherA, A> : public boost::mpl::true_ {};

и реализовать функцию convert, которую можно найти через ADL.

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

Теперь к актуальному вопросу (ам): какой предпочтительный способ связать типы для неявного преобразования? Мои предложения хорошие идеи? Есть ли недостатки у любого подхода? Опасны ли такие преобразования? Если разработчики библиотек вообще предоставляют второй метод, когда есть вероятность, что их тип будет реплицирован в программном обеспечении, с которым они, скорее всего, используются (я имею в виду промежуточное программное обеспечение для 3D-рендеринга, где большинство этих пакетов реализуют 3D вектор).

Ответы [ 6 ]

7 голосов
/ 16 января 2011

Я бы предпочел ваш "прокси" подход, чем другие варианты, если бы вообще его беспокоил.

Правда в том, что я обнаружил, что это настолько серьезная проблема во ВСЕХ сферах разработки, что я стараюсь избегать использования какой-либо конкретной библиотечной конструкции вне моего взаимодействия с этой конкретной библиотекой. Одним из примеров может быть работа с событиями / сигналами в различных библиотеках. Я уже выбрал boost как интеграл для своего собственного кода проекта, поэтому я вполне целенаправленно использую boost :: signal2 для всех коммуникаций в моем собственном коде проекта. Затем я пишу интерфейсы в используемую библиотеку пользовательского интерфейса.

Другой пример - строки. Каждая чертова библиотека пользовательского интерфейса изобретает эту строку заново. Вся моя модель и код данных используют стандартные версии, и я предоставляю интерфейсы для моих оболочек пользовательского интерфейса, которые работают с такими типами ... преобразование в конкретную версию пользовательского интерфейса только в тот момент, когда я непосредственно взаимодействую с компонентом пользовательского интерфейса.

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

Так что, в принципе, я бы предпочел прокси-подход, потому что я уже делаю это. Я работаю в абстрактных слоях, которые отделяют меня от какой-либо конкретной библиотеки, которую я использую, и делим эти абстракции на подклассы со спецификой, необходимой для взаимодействия с указанной библиотекой. Я ВСЕГДА этим занимаюсь, поэтому мне интересно узнать о какой-то небольшой области, где я хочу делиться информацией между двумя сторонними библиотеками.

1 голос
/ 31 января 2011

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

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

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

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

struct A {
    A(float x) : x(x) {}
    int x;
};

struct B {
    B(int y): y(y) {}
    template<class T> B(const T &t) { *this = convert(t); }
    int y;
};

inline B convert(const A &a) {
    return B(a.x+1);
}

В этом случае отключение конструктора шаблона изменит значение B (20,0).Другими словами, просто добавив неявный конструктор преобразования, вы можете изменить интерпретацию существующего кода.Очевидно, это очень опасно.Таким образом, неявное преобразование не должно быть общедоступным, а должно предоставляться для очень специфических типов, только когда оно ценно и понятно.Это не было бы достаточно распространенным, чтобы оправдать ваш второй вариант.

Подводя итог: это было бы лучше сделать вне библиотек, с полным знанием всех типов, которые будут поддерживаться.Прокси-объект кажется идеальным.

1 голос
/ 28 января 2011

Вы можете написать класс преобразователя (некоторый прокси), который может неявно преобразовывать несовместимые типы в и из них.Затем вы можете использовать конструктор для генерации прокси из одного из типов и передать его методу.Возвращенный прокси-сервер затем будет приведен непосредственно к нужному типу.

Недостатком является то, что вам нужно заключить параметр во все вызовы.Если все сделано правильно, компилятор даже встроит полный вызов, не создавая прокси.И нет никакой связи между классами.Только классы Proxy должны знать их.

Уже давно я программирую на C ++, но прокси должен выглядеть примерно так:

class Proxy { 
  private:
    IncompatibleType1 *type1;
    IncompatibleType2 *type2;
    //TODO static conversion methods
  public:
    Proxy(IncompatibleType1 *type1) {
      this.type1=type1;
    }
    Proxy(IncompatibleType2 *type2) {
      this.type2=type2;
    }
    operator IncompatibleType1 * () { 
      if(this.type1!=NULL)
        return this.type1;
      else
        return convert(this.type2);
    }
    operator IncompatibleType2 * () { 
      if(this.type2!=NULL)
        return this.type2;
      else
        return convert(this.type1);
    }
}

Вызовы всегда будут выглядеть так:

expectsType1(Proxy(type2));
expectsType1(Proxy(type1));
expectsType2(Proxy(type1));
0 голосов
/ 30 января 2011

Я медленный сегодня. В чем была проблема с использованием шаблона прокси снова? Мой совет, не тратьте много времени на беспокойство о том, что функции копирования выполняют ненужную работу. Кроме того, явно хорошо.

0 голосов
/ 28 января 2011

Не могли бы вы использовать перегрузку оператора разговора? как в следующем примере:

class Vector1 {
  int x,y,z;
public:
  Vector1(int x, int y, int z) : x(x), y(y), z(z) {}
};

class Vector2 {
  float x,y,z;
public:
  Vector2(float x, float y, float z) : x(x), y(y), z(z) {}

  operator Vector1()  {
    return Vector1(x, y, z);
  }
};

Теперь эти вызовы успешны:

void doIt1(const Vector1 &v) {
}

void doIt2(const Vector2 &v) {
}

Vector1 v1(1,2,3);
Vector2 v2(3,4,5);
doIt1(v1);
doIt2(v2);

doIt1(v2); // Implicitely convert Vector2 into Vector1
0 голосов
/ 16 января 2011

Относительно вашего первого варианта:

Укажите прокси-тип, который реализует операторы преобразования и конструкторы преобразования (и задания) для всех вовлеченных типы, и всегда используйте это.

Вы можете использовать строки (текст) в качестве прокси, если производительность не критична (или, возможно, если это так, и данные в любом случае являются в основном строками). Реализуйте операторы << и >>, и вы можете использовать boost::lexical_cast<> для преобразования с использованием текстового промежуточного представления:

const TargetType& foo = lexical_cast<TargetType>(bar);

Очевидно, что если вы очень обеспокоены производительностью, вы не должны этого делать, и есть и другие предостережения (оба типа должны иметь разумные текстовые представления), но это довольно универсально и «просто работает» со многими существующими вещами .

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