在使用C++和STL实现Observer模式时,我遇到了一个有趣的问题.考虑这个经典的例子:
class Observer {
public:
virtual void notify() = 0;
};
class Subject {
public:
void addObserver( Observer* );
void remObserver( Observer* );
private:
void notifyAll();
};
void Subject::notifyAll() {
for (all registered observers) { observer->notify(); }
}
Run Code Online (Sandbox Code Playgroud)
这个例子可以在每本关于设计模式的书中找到.不幸的是,现实系统更复杂,所以这是第一个问题:一些观察者决定在收到通知时将其他观察者添加到主题.这使我使用的"for"循环和所有迭代器无效.解决方案相当简单 - 我为已注册的观察者列表创建快照并迭代快照.添加新观察者不会使快照无效,所以一切似乎都可以.但是这里出现了另一个问题:观察者决定在收到通知后自行销毁.更糟糕的是,一个观察者可以决定销毁所有其他观察者(它们是从脚本控制的),这会使队列和快照无效.我发现自己在重新分配的指针上进行迭代.
我的问题是,当观察者互相残杀时,我应该如何处理这些情况呢?有没有现成的模式?我一直认为"观察者"是世界上最简单的设计模式,但现在似乎并不容易正确实现它......
谢谢大家,感谢您的关注.让我们有一个决定摘要:
[1]"不要这样做"对不起,但这是必须的.观察者从脚本控制并被垃圾收集.我无法控制垃圾收集以防止它们的取消分配;
[2]"使用boost :: signal"最有希望的决定,但我不能在项目上引入提升,这样的决定必须由项目负责人做出(我们在Playstation下编写);
[3]"使用shared__ptr"这将阻止观察者解除分配.一些子系统可能依赖于内存池清理,所以我认为我不能使用shared_ptr.
[4]"推迟观察者释放"队列观察员在通知时删除,然后使用第二个周期删除它们.不幸的是,我无法阻止重新分配,所以我使用了某种"适配器"包装观察者的技巧,实际上保留了"适配器"列表.在析构函数中,观察者从他们的适配器取消分配,然后我采取第二个周期来销毁空适配器.
ps是没关系,我编辑我的问题来总结所有的帖子?我是StackOverflow上的菜鸟......
我有一些C++代码,其中声明了以下枚举:
enum Some
{
Some_Alpha = 0,
Some_Beta,
Some_Gamma,
Some_Total
};
int array[Some_Total];
Run Code Online (Sandbox Code Playgroud)
Alpha,Beta和Gamma的值是顺序的,我很乐意使用以下循环来迭代它们:
for ( int someNo = (int)Some_Alpha; someNo < (int)Some_Total; ++someNo ) {}
Run Code Online (Sandbox Code Playgroud)
这个循环没问题,直到我决定改变枚举中声明的顺序,比如说,使Beta成为第一个值,而Alpha - 第二个.这使循环标头无效,因为现在我必须从Beta迭代到Total.那么,迭代枚举的最佳实践是什么?我希望迭代所有值,而不是每次都更改循环标题.我能想到一个解决方案:
enum Some
{
Some_Start = -1,
Some_Alpha,
...
Some_Total
};
int array[Some_Total];
Run Code Online (Sandbox Code Playgroud)
并从(Start + 1)迭代到Total,但它看起来很难看,我从未见过有人在代码中这样做.是否有任何众所周知的范例来迭代枚举,或者我只需要修复枚举值的顺序?(让我们假装,我真的有一些很棒的理由来改变枚举值的顺序)......
我正在开发一个简单的基于图块的2D游戏.我有一个级别,填充了可以与瓷砖互相交互的对象.检查与tilemap的碰撞是相当容易的,并且可以对具有线性复杂性的所有对象进行.但现在我必须检测对象之间的碰撞,现在我必须检查每个对象与其他所有对象,这会导致方形复杂性.
我想避免方形复杂性.是否有任何众所周知的方法来减少对象之间的冲突检测调用.是否存在任何数据结构(如BSP树),它们易于维护并允许一次拒绝许多冲突.
例如,关卡中的对象总数约为500,其中大约50个一次在屏幕上显示...
谢谢!
我在main函数的堆栈中有一些对象:
int main(...)
{
CFoo foo;
CBar bar;
}
Run Code Online (Sandbox Code Playgroud)
另外,我有一个函数,可以跟踪我的应用程序中的错误:
void Err(std::string msg)
{
SomehowLogErrorMessage(msg);
exit(1);
}
Run Code Online (Sandbox Code Playgroud)
当我必须报告致命错误时,Err函数绝对有用.我只是记录错误并终止应用程序 - 在发生此类错误后无法恢复.但是,以"exit()"结尾不会调用foo和bar析构函数 - 这是我实际预期的行为(但是错误)."abort()"也无济于事.另外,我不能在main()中使用异常来捕获它们.有没有其他方法来实现Err函数,它终止应用程序并正确清理堆栈对象?或者我应该以某种方式重新设计我的错误处理?
谢谢!
ps顺便说一句,我不能将WM_QUIT消息发送到我的主窗口?我对WinAPI并不擅长,但我的应用程序是纯Win32,而我的Err()函数可以获得我的主窗口的句柄.它会起作用吗?
提供,我想将一个可修改的参数传递给一个函数,我应该选择什么:通过指针传递它还是通过引用传递它?
我问这个是因为我一直认为通过引用(1)传递参数是最好的做法,但在检查了一些本地代码数据库后,我得出结论,最常见的方法是(2).此外,该男子本人(Bjarne Stroustrup)建议使用(2).(1)和(2)的[dis]优点是什么,还是仅仅是个人品味的问题?
我有一个类封装了一些算术,比方说定点计算.我喜欢重载算术运算符的想法,所以我写了以下内容:
class CFixed
{
CFixed( int );
CFixed( float );
};
CFixed operator* ( const CFixed& a, const CFixed& b )
{ ... }
Run Code Online (Sandbox Code Playgroud)
一切正常.我可以写3*CFixed(0)和CFixed(3)*10.0f.但是现在我意识到,我可以用整数操作数实现operator*更加有效.所以我重载它:
CFixed operator* ( const CFixed& a, int b )
{ ... }
CFixed operator* ( int a, const CFixed& b )
{ ... }
Run Code Online (Sandbox Code Playgroud)
它仍然有效,但现在CFixed(0)*10.0f调用重载版本,将float转换为int(我希望它将float转换为CFixed).当然,我也可以重载浮点版本,但它似乎是我的代码组合爆炸.有没有解决方法(或者我设计我的课程错了)?如何告诉编译器只使用int调用operator*的重载版本?
我正在尝试使用OpenGL为Android上的游戏编写一些骨架.我想知道,我应该在哪里放置我的主游戏循环代码?
到目前为止,我最好的候选者是Renderer.onDrawFrame(...)方法,它似乎被称为每帧,因此代码如下所示:
void onDrawFrame(GL10 gl)
{
preLoopActions();
m_gameScene->onUpdate();
m_gameScene->onRender(gl);
postLoopActions();
}
Run Code Online (Sandbox Code Playgroud)
有没有更好的方法?我不喜欢这个因为1)我必须在这个地方混合更新和渲染,android希望我只是渲染,2)这个方法似乎是从一个单独的"渲染线程"调用,这增加了游戏的复杂性.
我目前正在开发一个大型项目,我大部分时间都在调试.虽然调试是一个正常的过程,但是存在不稳定的错误,这些错误对于开发人员来说是最大的痛苦.该程序不起作用,有时候......有时它确实如此,而且你无能为力.
可以对这些错误做些什么?最常见的调试工具(交互式调试器,监视器,日志消息)可能无处可导,因为错误将消失...只是再次出现,稍后再出现.这就是为什么我要求一些启发式方法:这些错误最常见的原因是什么?我们应该调查哪些可疑代码来找到这样的错误?
让我开始列表:
还有什么?
有没有办法检测,指针是否指向C++中的数组?我的问题是我想实现一个类,它成为数组的所有者.我的类用指针初始化,我想知道,指针是否真的是一个数组指针.这是简化的代码:
class ArrayOwner {
public:
explicit ArrayOwner( int* initialArray ) : _ptrToArray(initialArray) {}
virtual ~ArrayOwner() { delete [] _ptrToArray; }
private:
int* _ptrToArray;
}
Run Code Online (Sandbox Code Playgroud)
我想在构造函数中添加assert,"initialArray"指针实际上是一个数组指针.我不能改变构造函数的契约,使用向量等有没有办法在C++中编写这个断言?
我正在使用 Android 的 GPUImage 库对位图应用一些效果。本质上,GPUImage接受位图并使用OpenGL ES,将1 x 1立方体渲染到位图大小的帧缓冲区中。用户可以编写自定义片段着色器来控制输出。
我正在尝试编写一个片段着色器,它在给定旋转角度的情况下旋转位图。这是我到目前为止所拥有的:
varying vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
uniform vec2 inputImageTextureSize;
uniform float angle;
const vec2 HALF = vec2(0.5);
void main()
{
float aSin = sin(angle);
float aCos = cos(angle);
vec2 tc = textureCoordinate;
tc -= HALF.xy;
tc *= mat2(aCos, aSin, -aSin, aCos);
tc += HALF.xy;
vec3 tex = texture2D(inputImageTexture, tc).rgb;
gl_FragColor = vec4(tex, 1.0);
}
Run Code Online (Sandbox Code Playgroud)
它可以工作,但显然会扭曲位图(因为 GPUImage 视口设置为将 1 x 1 立方体映射到实际位图大小)。我不知道如何消除扭曲。我应该根据角度和 inputImageTextureSize 应用一些额外的缩放变换,但我未能获得正确的方程。StackOverflow 上的答案实际上都不适合我的情况。请帮忙!
谢谢!
大家问候!
检查我自己的代码,我走到了这个有趣的路线:
const CString &refStr = ( CheckCondition() ) ? _T("foo") : _T("bar");
Run Code Online (Sandbox Code Playgroud)
现在我完全不知所措,无法理解为什么它是合法的.据我所知,必须使用r值或l值初始化const引用.未初始化的引用不可存在.但是()?operator在为引用赋值之前执行CheckCondition()函数.我现在可以看到,当执行CheckCondition()时,refStr存在,但仍未初始化.如果CheckCondition()将抛出异常,或使用goto语句传递控件,会发生什么?它是否会使参考文献未初始化或者我遗漏了什么?