[c++] использование классов во время их объявления

Landstreicher

Насколько разрешен сабж?
Например, у меня не компилится следующая программа:

template <typename T> class A {
public:
enum { N = 1 };
T x;
};
struct B
{
int x;
enum { M = A<B>::N };
};

Компилятор gcc 4.0, 4.1.
Соответствует ли такое поведение стандарту? Если да, то как можно выкрутиться, если нужно описать структуру данных, ссылающуюся на себя.

okunek

T *x
иначе получается, что оба класса требуют, чтобы каждый из них был полностью задефайнен. замкнутый круг.

kokoc88

Если да, то как можно выкрутиться, если нужно описать структуру данных, ссылающуюся на себя.
struct S
{
S* pS;
};
Или тебе нужно что-то особенное?

Slavaga

Жесть какая-то. Зачем так сложно? Наверняка можно как-то проще сделать.
У тебя значения констант меняться должны в разных классах или что? Если да, то вот такой код компилиться, хотя на мой взгля .... ладно промолчу.
У меня вот так скомпилилось:
template <typename T> class A
{
public:
enum { N = 1 } asd;
T x;
};
struct B;
class A<B>
{
enum { N = 7 };
};
struct B
{
int x;
enum { M = A<B>::N };
};

Maurog

а нужного эффекта такой код не даст?


class B {
public:
int x;
enum { M = A<char>::N };
};

vladan67

олько А(В*)
или никак

tamusyav

Если заведомо нет зависимости от типа (как в данном случае то вполне сойдет и вариант -а. Если зависимость есть (или возможна то получается цикл, который компилятор не разрулит (значение M зависит от N, N зависит от B, а B содержит M, то есть M зависит от самого себя). предложил вариант, в котором этот цикл разрулен вручную (то есть для A<B> фактически шаблон не используется но при этом внутрь класса A<B> нельзя поместить строчку B x; как сделано выше для шаблона (хотя можно поставить B* x, но это уже будет другой класс); это плохо, потому что тогда класс A<B> существует, но не является аналогом других классов с шаблоном A<>.
Вообще говоря, подобные циклические конструкции весьма сложны для анализа и отладки, и, имхо, лучше их избегать. То есть желательно, чтобы либо A ничего не знал о B (как у либо B ничего не знал об A.

Landstreicher

Спасибо всем ответившим!
Зависимость здесь по существу, попытаюсь объяснить подробнее.
Есть некий класс A, который содержит данные и указатель на какие-то другие елементы класса A, по типу списка, дерева. Например так:

class A
{
  int data;
  A* left;
  A* right
};

тут все понятно - работает без проблем.
Далее - A* меняется на продвинутые (smart) указатели, например что-нибудь в духе

class A
{
  int data;
  shared_ptr<A> left;
  shared_ptr<A> right;
}

Такое тоже более-менее работает. Допустим shared_ptr реализуется так:

template <typename T> class RCBase
{
public:
  int refcount;
  T data;
};
template <typename T> class shared_ptr
{
private:
  RCBase<T> *ptr;
public:
  ...
  shared_ptr& operator = (constr shared_ptr& b) {
    RCBase<T> old = ptr;
    ptr = b.ptr;
    if (ptr) ptr->refcount++;
    if (old) old->refcount--;
  }
  // не буду вдаваться в детали, здесь это неважно
}

Что мы видим: shared_ptr<T> использует RCBase<T>, RCBase<T> использует T, A использует shared_ptr<A> --- то есть получается цикл.
Я привел пример с enum, потому что он у меня не компилися. Ладно, допустим c enum-ами я как-нибудь разобрался, фиг с ним. В приведенном примере все равно получается цикл. Причем цикл по существу, я не вижу нормальных способов его устранить. Соответственно, вопрос: как надо писать классы A, shared_ptr, RCBase, чтобы вся эта конструкция скомпилилась? На примере enum я понял, что как попало писать нельзя, надо аккуратно. Какие еще есть ограничения?

Sebasten

Здесь ты присваиваешь объекту указатель на этот объект
shared_ptr& operator = (constr shared_ptr& b) {
RCBase<T> old = ptr;

old тоже должен быть
RCBase<T>* old
и вроде пока всё нормально)

Sebasten

А каким компилятором у тебя это компилится?
как-то странно выглядит
class A<B> {
};

Slavaga

Borland'овский компилятор, в Builder C++
Вообще, должно работать на других тоже - я не раз встречал такое объявление.

Sebasten

А какое имя у описанного класса?

Landstreicher

> old тоже должен быть
> RCBase<T>* old
Да, здесь я опечатался, имелось ввиду *
> и вроде пока всё нормально)
В таком виде как я написал --- компилится. Но тут есть два вопроса:
1) Это компилится конкретно моим компилятором. Насколько это переносимо? Будет ли это компилится другими компиляторами? Насколько это соотвествует стандарту?
2) То, что я описал --- это в некотором роде скелет, каркас. Простой пример, показывающий, как циклические зависимости между классами иногда действительно нужны. В реальности, в программе должна быть еще куча разных содержательных методов. И в них как раз не понятно, что можно использовать, а что нельзя, чтобы случайно не нарваться на какой-нибудь 'Incomplete type ...'

Sebasten

Не, в таком виде, как ты написал - у меня не компилится в восьмой студии, ну точней не "инстантинируется")

class A obj1, obj2;
obj1 = obj2;
c:\documents and settings\dmitry\my documents\visual studio 2005\projects\cmasmastest\cmasmastest.cpp(22) : error C2440: 'initializing' : cannot convert from 'RCBase<T> *' to 'RCBase<T>'
Думаю, что всёже не так-то и просто пример такой хитрой зависимости привести, чтоб реально нужно было, хотя вопрос, конечно, и интересный)

Slavaga

А какое имя у описанного класса?
A<B>

tamusyav

1) Если не ошибаюсь, вполне соответствует.
2) Важно, чтобы все конструкции, которые расположены в объявлении класса shared_ptr вне его методов, не использовали класс A непосредственно, а только через указатель, то есть не привязывались к его структуре. Внутри методов (в том числе inline) этот указатель уже можно разыменовывать, так как при этом циклической зависимости не возникает.
P.S. Небольшой оффтопик: не стоит ли RCBase описать внутри класса shared_ptr?
Оставить комментарий
Имя или ник:
Комментарий: