1

I'm learning C++ using Xcode and have written several small programs including a hangman game but I'm having trouble every time I try to separate a class into definition and implementation. I made a simple case that shows my problem. Short version is it seems that I need to specify a type in the implementation file even though it is already defined in the header file. I get "C++ requires a type specifier for all declarations" on lines 12 and 13 in my example. But if I change line 12, for example, to

 int xmlelem::atrb_count = 0;

it gets the error "non-static data member defined out-of-line". In other cases I have got an error saying that I was trying to redefine something. I think I'm missing a fundamental concept somewhere. I did not see this particular issue in the handful of similar questions I looked at.

xmlelem.hpp

//  xmlelem.hpp
//  learn header
//
//

#ifndef xmlelem_hpp
#define xmlelem_hpp

#include <stdio.h>
#include <string>

#endif /* xmlelem_hpp */

class xmlelem {
    
private:
    int atrb_count;
    std::string tag_name;
    
public:
    xmlelem(std::string tag);
    void add_atrib();
    std::string output();
};

xmlelem.cpp

//  xmlelem.cpp
//  learn header
//.
//

#include "xmlelem.hpp"
#include "string"
#include <iostream>

// line 11
xmlelem::atrb_count = 0;
xmlelem::tag_name = "";

xmlelem::xmlelem(std::string tag){
    tag_name = tag;
}

void  xmlelem::add_atrib(){
    atrb_count++;
}

std::string xmlelem::output(){
    std::string build = "<";
    build = build + tag_name + " " + std::to_string(atrb_count);
    build = build + ">";
    return build;
}

and main.cpp

//  main.cpp
//  learn header
//
//

#include <iostream>
#include "xmlelem.hpp"
using namespace std;

int main(){
    xmlelem clip("test)");
    std::cout << clip.output() << " test \n";
}
JaMiT
  • 14,422
  • 4
  • 15
  • 31
George White
  • 125
  • 6
  • I guess it should be ``xlelem::atrb_count`` instead of ``mlelem::atrb_count``. – emegona Aug 31 '20 at 23:53
  • yes - besides anything else there is a typo s/b xmlelem. – George White Sep 01 '20 at 00:42
  • @JaMiT - In comments on other questions I see people asking to see example code, – George White Sep 01 '20 at 00:43
  • @GeorgeWhite Yes, **example** code. A question without code tends to be poor quality. A question that is primarily code tends to be poor quality. A question that uses *both* text and code has a better chance of being well-received. – JaMiT Sep 01 '20 at 01:04
  • ... However, I am reconsidering my earlier request. I realized that if you search for the second error message, this question is near the top of the list. For that reason, I think it might be worth posting an answer that addresses both possibilities, and leave the question neutral as to which is desired. – JaMiT Sep 01 '20 at 01:06

2 Answers2

2

Let's take a look at the (second) error message.

non-static data member defined out-of-line

There are two parts to the error: "non-static data member" and "defined out-of-line". These are incompatible, so one of them must be changed. Furthermore, only one of them should be changed, or else you may run into a different problem. Decide which of the two parts is correct for your situation.

Keep "defined out-of-line"

When the line

int xmlelem::atrb_count = 0;

is encountered at namespace scope (that is, in neither a function nor a class/struct/union definition), it is an out-of-line definition. This definition tells the compiler to reserve, right at that spot, enough space for an int. Then whenever any xmlelem object accesses the atrb_count member, it will access this particular space. So there is one int shared by all objects.

However, this behavior corresponds to a static member. To make the declaration agree with the implementation, the keyword static needs to be added.

class xmlelem {
    
private:
    static int atrb_count;
    /* rest of the class definition */
};

Keep "non-static"

A non-static data member is stored inside each object of the class. Each object can do what it wants with its copy of the data without impacting other objects. So telling the compiler to reserve space outside the objects is contradictory. Simply removing the out-of-line definition is enough to get rid of the error message, but presumably you wanted that initialization to occur somewhere, right?

The initialization of non-static data members can be done either in-line or in a constructor. An example of moving the initialization in-line is the following.

class xmlelem {
    
private:
    int atrb_count = 0;
    /* rest of the class definition */
};

This is sometimes reasonable, but the stated goal was to separate the interface from the implementation. Therefore, it might be undesirable for the initial value of 0 to appear in the header file, as it does in the above. The alternative is to move the initial value to the constructor (to each constructor, if you had more than one).

xmlelem::xmlelem(std::string tag) :
    atrb_count(0),
    tag_name(tag)
{
}

(I've also taken the liberty of moving the initialization of tag_name into the initialization list.)

Remember, if you have more than one constructor, this needs to be done in each constructor that actually utilizes the default value (for an exception, think "copy constructor"). Repeated code is a drawback; it is up to you to decide if the gains are worth the cost.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
1

Remember that you are declaring a class. A class is an abstract concept. When you do this xlemem::atrb_count = 0;, you are having a concrete value on an abstract concept. Doesn't make sense, right? You don't think of a particular color when you think of the general concept of dog. Any initiliazations should be done inside the constructor, because only in the constructor is that we create a concrete object.

Therefore, you should eliminate lines 11 and 12 where you initialize these 2 attributes and your constructor code should be changed to:

xmlelem::xmlelem(std::string tag){
    tag_name = tag;
    atrb_count = 0;
}

Note that it isn't necessary to initialize a string to "".

emegona
  • 168
  • 1
  • 12