不小心使用模板会导致膨胀。避免这种膨胀的一种方法是使用一个薄的类型安全模板来包装非类型安全的非模板代码。要做到这一点,包装器需要为非模板代码提供某种方式来访问它不知道的东西。
例如,在数据结构中,包装器定义节点结构。不安全的代码需要读写节点,但必须通过包装器指定的某种接口间接读写节点。
实现此接口的一种方法是用包装器确定的函数指针和常量等详细信息填充结构(由不安全代码定义)。一种相关的常数是特定场的偏移量(在某些结构中)。不安全的代码可以使用该偏移量(和一些指针算法)直接访问该字段。
这是一个问题,尽管-随着乐观主义者变得更积极,这可能导致指针别名问题。如果节点可以从库中退出,则情况尤其如此。例如,可以从二叉树中提取节点并重新链接以形成链接列表。另一个令人恼火的例子是在单元测试时发生的。
我目前有一个容器库是沿着这些行编写的,它目前不会导致任何问题,但很快就会出现。它避免这些问题的原因是,所有的单元测试都应用于容器(而不是底层代码),并且节点永远不会逃离容器。也就是说,节点总是以相同的指针算术方式访问,因此不会出现指针别名优化问题。
不幸的是,我很快就需要允许从容器中提取节点,我可能还需要对底层不安全代码进行单元测试。
我没有处理这个特定的库,而是从这里的一个旧的二叉树库中提取了一个更简单的内容,它也遇到了同样的问题。在VC++9中,它只是起作用。使用mingwGCC4.4.0,调试构建可以工作,但发布构建失败。问题是,优化器发现指针别名的内联和失败的混合。
只是说清楚-我不想在这里“wtf-goto!!或者什么。该问题正在解决优化/指针问题。但是如果你能找到写作的方法
Tree_To_List
这是正确的结构,不使用隐藏/伪装的goto来实现,我感兴趣。
此外,还缺少一个基于模板的抽象层(模板c_-bin_-tree_工具不能完成整个工作-c_工具完成包装,但以每次使用的方式完成,而不是以可重用的形式完成。这只是提取相关代码的副作用。
这段代码所做的是通过逐个插入节点来创建一个不平衡的二叉树,然后平衡该树。平衡的工作方式是将树转换为列表(以某种方式已经是列表),然后将列表转换回树。树在平衡前后都被倾倒到stdio。
bintree.h
…
inline void* Ptr_Add (void* p1, std::ptrdiff_t p2) { return (void*) (((std::ptrdiff_t) p1) + p2); }
struct c_Bin_Tree_Closure
{
typedef int (*c_Node_Comp) (void* p_Node1, void* p_Node2);
c_Node_Comp m_Node_Comp;
std::ptrdiff_t m_Left, m_Right;
};
class c_BT_Policy_Closure
{
private:
const c_Bin_Tree_Closure* m_Closure;
protected:
void** Left_Of (void* p) { return ((void**) Ptr_Add (p, m_Closure->m_Left )); }
void** Right_Of (void* p) { return ((void**) Ptr_Add (p, m_Closure->m_Right)); }
int Compare_Node (void* p_Node1, void* p_Node2) const
{
return m_Closure->m_Node_Comp (p_Node1, p_Node2);
}
public:
c_BT_Policy_Closure ()
{
m_Closure = 0;
}
void Set_Closure (const c_Bin_Tree_Closure& p_Closure)
{
m_Closure = &p_Closure;
}
};
template<class tc_Policy>
class c_Bin_Tree_Tool : public tc_Policy
{
public:
c_Bin_Tree_Tool ()
{
}
void *List_To_Tree (size_t p_Size, void* &p_List);
void Tree_To_List (void* p_Root, void* &p_First, void* &p_Last, size_t &p_Size);
void Balance (void* &p);
void Insert (void* &p_Tree, void* p_Node);
};
template<class tc_Policy>
void* c_Bin_Tree_Tool<tc_Policy>::List_To_Tree (size_t p_Size, void* &p_List)
{
if (p_Size == 0) return 0;
size_t l_Size = p_Size / 2;
void* l_Ptr = List_To_Tree (l_Size, p_List);
void* l_This = p_List;
p_List = *tc_Policy::Right_Of (l_This);
*tc_Policy::Left_Of (l_This) = l_Ptr;
l_Size = p_Size - (l_Size + 1);
*tc_Policy::Right_Of (l_This) = List_To_Tree (l_Size, p_List);
return l_This;
}
template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Tree_To_List (void* p_Root, void* &p_First, void* &p_Last, size_t &p_Size)
{
// Use left links as prev links and right links as next links
void* l_Start = 0; // first-item-in-list pointer
void* l_Prev = 0; // previous node in list
void** l_Prev_Ptr = &l_Start; // points to the next (ie right) pointer for the next node.
void* l_Pos = p_Root;
void* l_Next;
void* l_Parent = 0;
size_t l_Count = 0;
p_Last = 0;
TOP_OF_LOOP:
l_Next = *tc_Policy::Left_Of (l_Pos);
if (l_Next != 0)
{
*tc_Policy::Left_Of (l_Pos) = l_Parent; // So we can find our way back up the tree
l_Parent = l_Pos;
l_Pos = l_Next;
goto TOP_OF_LOOP;
}
AFTER_STEP_PARENT:
l_Next = *tc_Policy::Right_Of (l_Pos);
*tc_Policy::Left_Of (l_Pos) = l_Prev;
p_Last = l_Pos;
l_Prev = l_Pos;
*l_Prev_Ptr = l_Pos;
l_Prev_Ptr = tc_Policy::Right_Of (l_Pos);
l_Count++;
if (l_Next != 0)
{
l_Pos = l_Next;
goto TOP_OF_LOOP;
}
if (l_Parent != 0)
{
l_Pos = l_Parent;
l_Parent = *tc_Policy::Left_Of (l_Pos);
goto AFTER_STEP_PARENT;
}
*l_Prev_Ptr = 0;
p_First = l_Start;
p_Size = l_Count;
}
template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Balance (void* &p)
{
void *l_First, *l_Last;
size_t l_Count;
Tree_To_List (p, l_First, l_Last, l_Count);
p = List_To_Tree (l_Count, l_First);
}
template<class tc_Policy>
void c_Bin_Tree_Tool<tc_Policy>::Insert (void* &p_Tree, void* p_Node)
{
void** l_Tree = &p_Tree;
while (*l_Tree != 0)
{
int l_Compare = tc_Policy::Compare_Node (*l_Tree, p_Node);
l_Tree = ((l_Compare < 0) ? tc_Policy::Right_Of (*l_Tree) : tc_Policy::Left_Of (*l_Tree));
}
*l_Tree = p_Node;
*tc_Policy::Right_Of (p_Node) = 0;
*tc_Policy::Left_Of (p_Node) = 0;
};
test_bintree.cpp
…
#include <iostream>
#include "bintree.h"
struct c_Node
{
c_Node *m_Left, *m_Right;
int m_Data;
};
c_Node g_Node0001 = { 0, 0, 1 }; c_Node g_Node0002 = { 0, 0, 2 };
c_Node g_Node0003 = { 0, 0, 3 }; c_Node g_Node0004 = { 0, 0, 4 };
c_Node g_Node0005 = { 0, 0, 5 }; c_Node g_Node0006 = { 0, 0, 6 };
c_Node g_Node0007 = { 0, 0, 7 }; c_Node g_Node0008 = { 0, 0, 8 };
c_Node g_Node0009 = { 0, 0, 9 }; c_Node g_Node0010 = { 0, 0, 10 };
int Node_Compare (void* p1, void* p2)
{
return (((c_Node*) p1)->m_Data - ((c_Node*) p2)->m_Data);
}
c_Bin_Tree_Closure g_Closure =
{
(c_Bin_Tree_Closure::c_Node_Comp) Node_Compare,
offsetof (c_Node, m_Left ), offsetof (c_Node, m_Right)
};
class c_Tool : public c_Bin_Tree_Tool<c_BT_Policy_Closure>
{
protected:
typedef c_Bin_Tree_Tool<c_BT_Policy_Closure> c_Base;
public:
c_Tool () { Set_Closure (g_Closure); }
void Insert (c_Node* &p_Tree, c_Node* p_Node) { c_Base::Insert ((void*&) p_Tree, p_Node); }
void Balance (c_Node* &p) { c_Base::Balance ((void*&) p); }
};
void BT_Dump (size_t p_Depth, c_Node* p_Node)
{
if (p_Node != 0)
{
BT_Dump (p_Depth + 1, p_Node->m_Left);
for (size_t i = 0; i < p_Depth; i++) std::cout << " .";
std::cout << " " << p_Node->m_Data << std::endl;
BT_Dump (p_Depth + 1, p_Node->m_Right);
}
}
int main (int argc, char* argv[])
{
c_Tool l_Tool;
c_Node *l_Root = 0;
l_Tool.Insert (l_Root, &g_Node0001);
l_Tool.Insert (l_Root, &g_Node0002);
l_Tool.Insert (l_Root, &g_Node0003);
l_Tool.Insert (l_Root, &g_Node0004);
l_Tool.Insert (l_Root, &g_Node0005);
l_Tool.Insert (l_Root, &g_Node0006);
l_Tool.Insert (l_Root, &g_Node0007);
l_Tool.Insert (l_Root, &g_Node0008);
l_Tool.Insert (l_Root, &g_Node0009);
l_Tool.Insert (l_Root, &g_Node0010);
BT_Dump (0, l_Root); std::cout << "----------" << std::endl;
l_Tool.Balance (l_Root);
BT_Dump (0, l_Root);
return 0;
}
预期结果是…
1
. 2
. . 3
. . . 4
. . . . 5
. . . . . 6
. . . . . . 7
. . . . . . . 8
. . . . . . . . 9
. . . . . . . . . 10
----------
. . . 1
. . 2
. 3
. . . 4
. . 5
6
. . . 7
. . 8
. 9
. . 10
实际发生的事情(mingwGCC4.4.0,优化的发布构建)
1
. 2
. . 3
. . . 4
. . . . 5
. . . . . 6
. . . . . . 7
. . . . . . . 8
. . . . . . . . 9
. . . . . . . . . 10
----------
1
据我所知,平衡操作运行正常,但bt_dump函数看不到
m_Left
和
m_Right
领域。
编辑
这是错误的-否则我为什么要将节点1视为新的根。我想这就是当你过于依赖对几个月前调查的记忆时会发生的事情。
编辑
实际上,作为根的节点1是一个问题,但是由于它是旧的根-好吧,最好忽略这一点,问题是什么,并制定出自己的理论;-)
就未定义的标准而言,规范中存在许多问题。我认为最大的问题是节点结构中的链接是C_节点*,但是由于不安全的代码对C_节点一无所知,所以它(通过指针算法)将它们作为void*进行访问。
一个解决办法是让不安全的代码通过getter和setter函数指针执行所有读写操作,避免所有指针算法,并确保所有对C节点实例的访问都是通过C节点*指针完成的。更妙的是,接口变成了一个带有getter/setter方法等的类。在完整的二进制树库中,我有其他的策略类来实现这一点,不过老实说,当问题出现时,我真正的解决办法是将所有代码都扔到一个“垃圾”文件夹中,而我很少使用它,而且可能在任何情况下都应该使用boost侵入列表。哎呀。
然而,这仍然会留下另一个更复杂、使用量更大的容器库,而这并不会消失。我想我只需要做非常痛苦的重构就可以去掉offsetof和pointer的算术运算,但是…
C++规则究竟是什么——当编译器精确地不可能发现一个可能的指针别名时?是否可以重写上面的二叉树代码,以便它仍然使用指针算法,仍然允许在库内和库外访问/修改节点,但不存在这种优化问题?