Подсчет ссылок в C ++ - PullRequest
       20

Подсчет ссылок в C ++

5 голосов
/ 16 декабря 2008

Я реализую математическую библиотеку в C ++. Библиотека будет скомпилирована в DLL, поэтому тем, кто ее использует, понадобятся только заголовочные файлы определений классов.

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

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

// Define a Bézier curve
CVecList pts;
pts.Add(Vector(0,0,0));
pts.Add(Vector(0,0,100));
pts.Add(Vector(0,100,0));
pts.Add(Vector(0,100,100));
CCurve* c1 = new CBezier(pts);

// Define a 3rd order B-Spline curve
pts.Clear();
pts.Add(Vector(0,0,0));
pts.Add(Vector(0,200,100));
pts.Add(Vector(0,200,200));
pts.Add(Vector(0,-200,100));
pts.Add(Vector(0,-200,200));
pts.Add(Vector(0,0,0));
CCurve* c2 = new CBSpline(pts,3);

// The Bézier curve object must be deleted automatically
// because the only reference to it has been released
// Similar to IUnknown::Release() in COM
c1 = c2;

Все становится немного сложнее, когда я определяю поверхностные объекты, потому что некоторые поверхности определены в виде двух кривых:

CVecList pts;
// ...
CCurve* f = new CBezier(pts);

pts.Clear();
// ...
CCurve* g = new CBezier(pts);

// Mixed surface: S(u,v) = (1-v)*f(u) + v*g(u)
CSurface* s = new CMixed(f,g);

// There are two references to the first Bézier curve,
// the first one is f
// the second one is hidden in a member of CMixed

// Something similar applies to the second Bézier curve

Я думал, что переопределение operator = для указателей могло бы помочь:

// This is what I tried, but it's illegal:
typedef CReferenceCounted* PRC;
PRC& operator =(PRC& dest, PRC& source)
{
    if (source)
        source->AddRef();
    if (dest)
        dest->Release();
    memcpy(&dest,&source,sizeof(PRC));
    return dest;
}

... но потом я обнаружил, что operator = недопустим, если он не является нестатическим членом класса.

Может ли кто-нибудь помочь мне?

Ответы [ 5 ]

11 голосов
/ 16 декабря 2008

Вы пытались перегрузить оператор для скалярных типов. C ++ не позволяет вам делать это, за исключением перечислений (помимо того, что operator = должен быть членом). По крайней мере, один из типов должен быть пользовательским типом. Таким образом, вы хотите заключить необработанный указатель в определенный пользователем класс, который перегружает конструктор, конструктор копирования, оператор назначения копирования и деструктор и выполняет правильный подсчет ссылок. Это идеальная ситуация для boost::shared_ptr, которая делает именно это:

boost::shared_ptr<CCurve> c1(new CBezier(pts));

То же самое касается поверхностей:

CVecList pts;
// ...
boost::shared_ptr<CCurve> f(new CBezier(pts));

pts.Clear();
// ...
boost::shared_ptr<CCurve> g(new CBezier(pts));

// Mixed surface: S(u,v) = (1-v)f(u) + vg(u)
boost::shared_ptr<CSurface> s(new CMixed(f,g)); 

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

Редактировать : Если вы собираетесь обернуть shared_ptr, вы можете сделать это, используя идиому pimpl (handle / body):

/* ---- wrapper in header file bezier.hpp */

struct CBezier {
    CBezier(CVecList const& list);
    void do_calc();
    // ...

private:
    struct CBezierImpl;
    boost::shared_ptr<CBezierImpl> p;
};

/* ---- implementation file bezier.cpp */

// private implementation
struct CBezier::CBezierImpl {
    CBezierImpl(CVecList const& list);
    void do_calc();
    // ...
};


CBezier::CBezier(CVecList const& list)
:p(new CBezierImpl(list)) {

}

void CBezier::do_calc() {
    // delegate to pimpl
    p->do_calc();
}

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

Если вы разрабатываете математическую библиотеку, потратьте много времени на размышления о том, могут ли ваши классы выглядеть как int или std :: complex. То есть, значения ведут себя как значения. Э.Г.

std::vector<math::point3d> pts;
pts.push_back(math::point3d(0,0,0));
pts.push_back(math::point3d(110,0,0));
pts.push_back(math::point3d(0,100,0));
pts.push_back(math::point3d(0,0,100));
CCurve c1 = make_bezier(pts);
0 голосов
/ 16 декабря 2008

Я согласен с Guishu и MSalters. Даже если это не курс по программированию, было бы неплохо имитировать более пристальный взгляд на математику (например, vector3 = vector1 + vector2 и т. Д.).

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

Обратите внимание, однако, что есть математические библиотеки, доступные для C ++ (TNT, вне моей головы). Рассматривали ли вы на этом свою работу?

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

Пользователями моих занятий будут люди, которые плохо знакомы с языком.

Ваш класс предназначен для курса программирования?

Если это так, я бы не использовал указатели и использовал только конструкторы копирования / присваивания:

  • Производительность / Память не является приоритетом
  • Самостоятельное управление памятью покажет довольно плохой пример использования new / delete
  • Использование любых интеллектуальных указателей без знания управления памятью может привести к путанице в дальнейшем.
0 голосов
/ 16 декабря 2008

Я бы порекомендовал intrusive_ptr вместо shared_ptr для объектов, которыми вы можете управлять для повышения производительности и удобства использования, поскольку позже вы можете назначить необработанный указатель на intrusive_ptr, поскольку счетчик ссылок встроен в объект.

...