2013-04-10 5 views
1

Я хочу избежать перекомпиляции всего, что включает в себя общедоступный заголовочный файл, только потому, что что-то изменилось в частной части определения класса. Я изучаю другие варианты рядом с PIMPL.partial class in C++

Это то, что я пробовал:

Я создал библиотеку, которая содержит класс А:

A_p.h содержит приватную часть класса А

void PrivateMethod(int i); 

Ах общедоступный заголовочный файл:

class A 
{ 
public: 
    A(); 
    virtual ~A(); 
    // other public members 
private: 
#ifdef A_PRIVATE 
#include "A_p.h" 
#endif 
}; 

a.cpp

#define A_PRIVATE 
#include "A.h" 

A::A() {} 
A::~A() {} 
void A::PrivateMethod(int i) { } 

Затем я создал консольный проект Win32, который включает в себя публичный заголовок (хиджры) и ссылки против файла .lib.

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

+6

«Я читал о Pimpl, но я искал способ, чтобы избежать этого.» Зачем? –

+3

Вы нарушаете ODR и вызываете UB. – PlasmaHH

+0

Итак, что произойдет, если вы измените частную часть? – juanchopanza

ответ

1

Абстрактные классы позволяют объявлять открытый интерфейс, но имеют личные данные и функции, путем подкласса абстрактного класса.

Ключевая причина, по которой это не будет работать так, как вы описываете, и поэтому не поддерживается в стандарте C++, заключается в том, что предлагаемое публичное заявление не позволяет узнать размер A. Публичная декларация не показывает, сколько места требуется для частных данных. Поэтому код, который видит только публичное объявление, не может выполнить new A, не может выделить место для определения массива A и не может выполнять арифметику с указателями на A.

Есть и другие проблемы, которые могут быть отработаны каким-то образом, например, где расположены указатели на элементы виртуальной функции. Однако это вызывает ненужные осложнения.

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

Затем в отдельном частном коде вы объявляете и определяете класс B, который является производным от A.Вам нужно будет предоставить способы создания и уничтожения объектов, вероятно, с помощью публичной «новой» функции, которая возвращает указатель на A и работает путем вызова частной функции, которая может видеть объявление B и общедоступную функцию «удалить», которая принимает указатель на A и работает, вызывая частную функцию, которая может видеть объявление B.

+0

Теперь это хорошая причина не использовать эту конструкцию. Размер класса действительно будет разные в lib и exe. –

6

«Все, кажется, работает» - выглядит. Вы просто испытываете неопределенное поведение. Это некорректная программа - определение класса должно быть одинаковым во всех единицах компиляции, которые используют этот класс.

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

+0

Нет ли способа обеспечить совместимость с двоичными файлами? –

+2

@WouterHuysentruit нет, потому что этот подход в корне неверен. –

+3

@WouterHuysentruit plus, вы ** должны ** использовать идиому PIMPL - это прямо здесь, является одной из причин, по которой она существует в первую очередь. –

1

Есть 3 хороших способов сокрытия такого рода информации:

  1. только вперед объявить ваш класс. Это работает только в том случае, если он просто передается через клиентский код (через указатели и/или ссылки), и только использует внутри вашей библиотеки. Ваша библиотека будет необходимо предоставить фабричные функции или подобное, чтобы вернуть указатель/ссылку в первую очередь, клиент никогда не может назвать new или delete

  2. разоблачить абстрактный базовый класс и снова обеспечивают фабричные функции (которые инстанцирует конкретный производный класс видимым только внутри библиотеки)

  3. использования Pimpl

Я также согласен, что вы должны reconsi любой класс настолько большой, что скрывать его необходимо, но если вы действительно не можете его разбить, это ваши варианты.


А как & почему нарушение ODR ломает вещи на практике:

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