简体   繁体   中英

Why do I get an “Access violation” in this C++ code

I have the following three classes:

class BeliefRoot
{
    std::string m_Name;
    BeliefRoot(std::string Name) : m_Name(Name)
    { }
};

template <class factType>
class Belief : public BeliefRoot
{
    factType m_Fact;
    explicit Belief(std::string UniqueName, factType InitialFact = NULL)
                                            : BeliefRoot(UniqueName), m_Fact(InitialFact)
    { }
};

template <class factType>
class BeliefSet : public Belief<factType>
{
    std::list<factType> m_Facts;
    explicit BeliefSet(std::string UniqueName) : Belief(UniqueName)
    { }
};

Now I want to instantiate the class BeliefSet two times:

BeliefSet<float> bSetFloat("SetFloat");
BeliefSet<std::string> bSetString("SetString");

The first is fine, but at the second call I get the following error:

0xC0000005: Access violation reading location 0x0000000000000000.

Can somebody explain why this happens, but only if used with std::string ?

factType is string. So it is equivalent to

string m_Fact = NULL;

factType InitialFact = NULL where factType = std::string will attempt to construct a std::string using the single argument const char* constructor. Constructing a std::string from a nullptr will cause this crash.

For a default parameter having generic (dependent on template parameters) type, you don't want = 0 (which is what you get with the NULL macro). It'll select a converting constructor for std::string instead of the default constructor, and that converting constructor forbids passing a null pointer value. The crash is a consequence of violating that precondition.

You also don't want default initialization, which leaves primitives with no initialization at all. The C++ concept of "value-initialization" serves you well here... default construction for types with non-trivial construction, and zero initialization otherwise.

Good options then are copy-initialization from a value-initialized default, or the very handy list-initialization with an empty list, which is very short syntactically and also works for aggregates (since C++11 it gives value-initialization for scalar types, before that it was only useful for aggregates):

/* C++03 value-initialization */
explicit Belief(std::string UniqueName, factType InitialFact = factType())

/* list-initialization, since C++11 this also works great for scalars */
explicit Belief(std::string UniqueName, factType InitialFact = {})

As others have noted, your problem is the =NULL ; below I describe it in detail, and also describe how to fix your code.

explicit BeliefSet(std::string UniqueName) :
  Belief<factType>(UniqueName)
{}

with factType = std::string , calls:

explicit Belief(
  std::string UniqueName,
  std::string InitialFact = NULL
) :
  BeliefRoot(UniqueName),
  m_Fact(InitialFact)
{}

and

std::string InitialFact = NULL

is illegal. Replace with ={} , giving you:

class BeliefRoot
{
  std::string m_Name;
  BeliefRoot(std::string Name):
    m_Name(Name)
  {}
};

template <class factType>
class Belief : public BeliefRoot
{
  factType m_Fact;
  explicit Belief(
    std::string UniqueName,
    factType InitialFact = {}
  ):
    BeliefRoot(UniqueName),
    m_Fact(InitialFact)
  {}
};

template <class factType>
class BeliefSet : public Belief<factType>
{
  std::list<factType> m_Facts;
  explicit BeliefSet(std::string UniqueName):
    Belief(UniqueName)
  {}
};

and your code should work.

Let's take a look at what BeliefSet<std::string> resolves to at compiletime:

//We're appending __string to indicate how the type will (roughly) be represented at compiletime.
class Belief__string : public BeliefRoot
{
    std::string m_Fact;
    explicit Belief(std::string UniqueName, std::string InitialFact = 0)
                                            : BeliefRoot(UniqueName), m_Fact(InitialFact)
    { }
};


class BeliefSet__string : public Belief__string
{
    std::list<std::string> m_Facts;
    explicit BeliefSet(std::string UniqueName) : Belief(UniqueName)
    { }
};

So the immediately suspicious code is in the intermediate class: std::string InitialFact = 0 . This is poorly formed, but what's almost certainly happening is the string is trying to have a null pointer assigned to it. Strings can receive const char * types for construction, so it may be trying to read from this null pointer, and immediately failing due to dereferencing null .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM