Странное поведение со статическим шаблоном класса - PullRequest
1 голос
/ 10 июля 2010

У меня возникли некоторые трудности с диагностикой ошибки сегментации, возникшей из-за статического класса шаблона или, по крайней мере, я так думаю (см. Оригинальный пост здесь Помогите понять segfault с помощью std :: map / boost:: unordered_map ).

С момента публикации я обнаружил в своей программе какое-то странное поведение.Я никогда не сталкивался с чем-то подобным, и мне кажется, что я не подозреваю о каких-то тонких подробностях работы шаблонов, в результате чего я где-то что-то делаю не так.проблема исчезла, но она все еще сохраняется.

Наиболее сложным симптомом является следующее: в одном исходном файле (Menu.cpp) у меня есть три следующих вызова:

Font::init();
// later
g_font = Font::get("Mono.ttf");
// later
Font::release();

В другом источникефайл (Game.cpp) У меня почти три одинаковые строки.Почти весь код в Menu.cpp выполняется до того, как что-либо в Game.cpp будет выполнено.

Теперь все в порядке с этим.Однако, если я просто закомментирую

g_font = Font::get("Arial.ttf");

в Game.cpp, программа обнаружит ошибку сегментации в g_font = ... в Menu.cpp (примечание: все в Menu.cpp и Game.cpp имеет свое собственное пространство имен).Но NO код даже был выполнен в Game.cpp на этом этапе!

Теперь, если я свяжу Menu.o до Game.o, проблема исчезнет (но другая проблема существует в других местах).Что я здесь не понимаю?

Я использовал отладчик, чтобы просмотреть программу и посмотреть, что происходит.Когда строка g_font закомментирована в Game.cpp, буст-контейнер (unordered_map) в базовом классе Font Resource почему-то не будет правильно инициализирован.В частности, buckets_ и size_ неинициализированы и приводят к ошибочному поведению.Я пытался использовать std :: map и аналогичные проблемы существуют.

Вот полный список для Font.hpp

#ifndef __Font_hpp__
#define __Font_hpp__

#include <string>
#include "Vector.hpp"
#include "Resource.hpp"

class FTPixmapFont;

/*!
 * Adapter class for FTGL's FTPixmapFont
 */
class Font : public Resource<Font>
{
public:
    Font(const std::string& fileName);
 ~Font();

 void render(const std::string& str, const Vector& pos, int ptSize=14) const;

private:
 FTPixmapFont *m_font;
};

#endif // __Font_hpp__

Вот полный список для Resource.hpp (он потенциально утечка памяти прямо сейчас на release()Я изначально использовал boost::shared_ptr в контейнере, но переключился на необработанные указатели, думая, что это все исправит, дох).

#ifndef __Resource_hpp__
#define __Resource_hpp__

#include <string>
#include <map>
#include <boost/unordered_map.hpp>
#include <boost/utility.hpp>
#include "debug.hpp"
#include "assert.hpp"

#define MAP_TYPE boost::unordered_map

/*!
 * Resource base class.
 */
template <class T>
class Resource : public boost::noncopyable
{
public:
 static void init(const std::string& dir="data")
 {
  ASSERT(!c_init);
  if (*dir.rbegin() == '/') {
   c_dataDirectory = dir;
  } else {
   c_dataDirectory = dir + '/';
  }
  c_init = true;
 }

 static void release()
 {
  ASSERT(c_init);
  c_dataDirectory.clear();
  c_resources.clear();
  c_init = false;
 }

 static const T *get(const std::string& fileName)
 {
  T *resource = NULL;

  typename MAP_TYPE<std::string, T*>::const_iterator itr = c_resources.find(fileName);
  if (itr == c_resources.end()) {
   resource = new T(c_dataDirectory + fileName);
   c_resources.insert(std::pair<std::string, T*>(fileName, resource));
  } else {
   resource = itr->second;
  }

  return resource;
 }

private:
 static bool c_init;
 static std::string c_dataDirectory;
 static typename MAP_TYPE<std::string, T*> c_resources;
};

template <class T> bool Resource<T>::c_init = false;
template <class T> std::string Resource<T>::c_dataDirectory;
template <class T> MAP_TYPE<std::string, T*> Resource<T>::c_resources;

#endif // __Resource_hpp__

Вот некоторые выходные данные (трассировка стека) из GDB с MAP_TYPE = boost::unordered_map:

Reading symbols from /home/tim/Projects/gameproj/app/game...done.
(gdb) r
Starting program: /home/tim/Projects/gameproj/app/game 
[Thread debugging using libthread_db enabled]

Program received signal SIGSEGV, Segmentation fault.
0x0000000000499aca in boost::unordered_detail::hash_table<boost::unordered_detail::map<std::string, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > > >::find_iterator (this=0x79bd80, 
    bucket=0x20, k=...) at /usr/local/include/boost/unordered/detail/table.hpp:55
55         node_ptr it = bucket->next_;
(gdb) bt
#0  0x0000000000499aca in boost::unordered_detail::hash_table<boost::unordered_detail::map<std::string, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > > >::find_iterator (this=0x79bd80, 
    bucket=0x20, k=...) at /usr/local/include/boost/unordered/detail/table.hpp:55
#1  0x0000000000499872 in boost::unordered_detail::hash_table<boost::unordered_detail::map<std::string, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > > >::find (this=0x79bd80, k=...)
    at /usr/local/include/boost/unordered/detail/table.hpp:583
#2  0x00000000004994fb in boost::unordered_map<std::string, Font*, boost::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string const, Font*> > >::find (this=0x79bd80, k=...)
    at /usr/local/include/boost/unordered/unordered_map.hpp:423
#3  0x00000000004992ab in Resource<Font>::get (fileName=...) at /home/tim/Projects/gameproj/app/Resource.hpp:45
#4  0x0000000000498e23 in Menu::init (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:57
#5  0x0000000000498ce1 in Menu::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:23
#6  0x0000000000481275 in Main::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Main.cpp:25
#7  0x0000000000481135 in main (argc=1, argv=0x7fffffffe398) at /home/tim/Projects/gameproj/app/main.cpp:10
(gdb) 

Вот некоторые выходные данные (трассировки стека) из GDB с MAP_TYPE = std::map:

Reading symbols from /home/tim/Projects/gameproj/app/game...done.
(gdb) r
Starting program: /home/tim/Projects/gameproj/app/game 
[Thread debugging using libthread_db enabled]

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff701ae4d in std::string::compare(std::string const&) const () from /usr/lib/libstdc++.so.6
(gdb) bt
#0  0x00007ffff701ae4d in std::string::compare(std::string const&) const () from /usr/lib/libstdc++.so.6
#1  0x0000000000480c2d in std::operator< <char, std::char_traits<char>, std::allocator<char> > (__lhs=..., __rhs=...)
    at /usr/include/c++/4.4/bits/basic_string.h:2320
#2  0x0000000000480a15 in std::less<std::string>::operator() (this=0x772d60, __x=..., __y=...)
    at /usr/include/c++/4.4/bits/stl_function.h:230
#3  0x0000000000480691 in std::_Rb_tree<std::string, std::pair<std::string const, Font*>, std::_Select1st<std::pair<std::string const, Font*> >, std::less<std::string>, std::allocator<std::pair<std::string const, Font*> > >::find (this=0x772d60, 
    __k=...) at /usr/include/c++/4.4/bits/stl_tree.h:1424
#4  0x0000000000480465 in std::map<std::string, Font*, std::less<std::string>, std::allocator<std::pair<std::string const, Font*> > >::find (this=0x772d60, __x=...) at /usr/include/c++/4.4/bits/stl_map.h:659
#5  0x000000000048027d in Resource<Font>::get (fileName=...) at /home/tim/Projects/gameproj/app/Resource.hpp:45
#6  0x000000000047ff97 in Menu::init (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:57
#7  0x000000000047fe55 in Menu::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Menu.cpp:23
#8  0x000000000046a725 in Main::run (xResolution=1024, yResolution=768) at /home/tim/Projects/gameproj/app/Main.cpp:25
#9  0x000000000046a5e5 in main (argc=1, argv=0x7fffffffe398) at /home/tim/Projects/gameproj/app/main.cpp:10

Я также использовал Valgrind, нет ошибок, кроме случаев, когда происходит ошибка сегментации.

Я использую CMake для генерации Makefiles, gcc 4.4.3 и boost 1.43.Я работаю на 64-битной машине Ubuntu.

Любая помощь по этому вопросу будет принята с благодарностью, я чувствую, что с ней ничего не получится.А пока я собираюсь попробовать собрать другую платформу / машину, чтобы увидеть, получаю ли я такое же поведение.

Ответы [ 2 ]

0 голосов
/ 12 октября 2010

Попробуйте использовать шаблон проектирования Singleton.Например, вы можете использовать Loki's SingletonHolder (http://loki -lib.sourceforge.net / index.php? N = Main.HomePage).

И если у вас есть правильная настройка объекта Singleton, вы можете пропустить статическийчлены, так как string и unordered_map в любом случае сохраняют все свои элементы динамически.Или сделайте их статическими указателями и обязательно удалите их правильно.

0 голосов
/ 23 июля 2010

Попробуйте изменить

template <class T> std::string Resource<T>::c_dataDirectory;
template <class T> MAP_TYPE<std::string, T*> Resource<T>::c_resources;

, чтобы явно использовать конструкторы по умолчанию?Возможно, это требуется для шаблонов - аннотации C ++ используют этот стиль, хотя они и не комментируют, почему.

Если никто еще не упомянул об этом, есть одна копия статического члена данных на класс.*

...