Я реализую математическую библиотеку на 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 = недействителен, если он не является нестатическим членом класса.
Может ли кто-нибудь мне помочь?
Я пытался сохранить свой шаблон кода свободным, но не могу придумать другого решения. Спасибо.
Эдуардо, не могли бы вы отредактировать этот пост, выбрать код в своем вопросе, затем нажать кнопку с нулями и единицами? Это применит подсветку синтаксиса к коду.
Я не знал этого трюка. (Я здесь новичок) Спасибо, TM.





Вы пытались перегрузить оператор для скалярных типов. C++ не позволяет вам делать это, кроме перечислений (кроме того, что оператор = должен быть членом). По крайней мере, один из типов должен быть типом, определяемым пользователем. Таким образом, вы хотите обернуть необработанный указатель в определенный пользователем класс, который перегружает конструктор, конструктор копирования, оператор присваивания копии и деструктор и выполняет правильный подсчет ссылок. Это идеальная ситуация для 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();
}
// ...
Я бы сделал что-то вроде определения CCurve как CCurved_internal, затем typedef CCurve для boost :: shared_ptr <CCurve_internal> ,,,
Я пытался сохранить свой шаблон кода свободным, но не могу придумать другого решения. Спасибо.
вы могли бы, если бы вы использовали CCurve как не-указатель и выделите в нем ресурс. а затем CCurve может производить подсчет ссылок, удаляя при необходимости управляемый указатель. вы можете просто обернуть shared_ptr и сделать его прозрачным для ваших учеников.
Пожалуйста, избегайте переопределения подсчета ссылок. Работать в многопоточной среде гораздо сложнее, чем вы думаете. К тому же реализация shared_ptr в Boost великолепна.
да, Пол, это идея обертывания shared_ptr. Я также не вижу причин изобретать велосипед
Я не буду изобретать велосипед. Я просто заверну shared_ptr ... Однако у меня нет предыдущего опыта работы с Boost, поэтому я, возможно, скоро вернусь с другими вопросами ... :)
Я полностью согласен с этим. Использование shared_ptr - лучший способ.
Я бы рекомендовал intrusive_ptr вместо shared_ptr для объектов, которыми вы можете управлять для повышения производительности и удобства использования, поскольку вы можете назначить необработанный указатель на intrusive_ptr позже, поскольку счетчик ссылок встроен в объект.
Если вы разрабатываете математическую библиотеку, потратьте много времени на размышления о том, могут ли ваши классы выглядеть как 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);
The users of my classes will be people who are new to the language.
Ваш класс предназначен для курса программирования?
Если это так, я бы избегал использования указателей и использовал только конструкторы копирования / присваивание:
Я согласен с Guishu и MSalters. Даже если это не для курса программирования, было бы неплохо более внимательно имитировать математический вид (например, vector3 = vector1 + vector2 и т. д.).
Что вы также можете сделать, так это использовать копирование при записи (перемонтирование является логическим следствием), но только внутренне. Это может дать вам достаточно быстрые назначения, исключить управление кучей на стороне клиента и сходство с математической нотацией.
Обратите внимание, однако, на то, что для C++ доступны математические библиотеки (TNT, вне моей головы). Вы думали о том, чтобы основывать свою работу на этом?