1

I'm making a simple programming language, and have encountered the following problem:

I have a Parser class which has methods that return derived classes of the Node struct. Currently all of the Parser class methods look something like this:

DerivedNode Parser::ParseDerived()
{
    DerivedNode node{};

    node.Field1 = 0;
    node.Field2 = 10;

    return node;
}

I recently switched the Node type from being a struct to a class, because I want to implement some oop, thus I made fields of Node class private. I'm refactoring the Parser class and struggling to decide, what is the best option out of these 3:

// Option 1: Public setters
DerivedNode Parser::ParseDerived()
{
    DerivedNode node{};

    node.SetField1(0);
    node.SetField2(10);

    return node;
}
// Option 2: Making Parser a friend of all Node derived classes
DerivedNode Parser::ParseDerived()
{
    DerivedNode node{};

    node.m_Field1 = 0;
    node.m_Field2 = 10;
    
    return node;

}
// Option 3: Storing in variables and calling a constructor
DerivedNode Parser::ParseDerived()
{
    size_t field1 = 0;
    size_t field2 = 10;

    return DerivedNode{ field1, field2 };
}

I would love to hear which of these methods is the best and some arguments why (sorry for my English).

Samo Chreno
  • 142
  • 1
  • 7
  • 1
    *I recently switched the Node type from being a struct to a class* -- There is no difference between a `struct` and a `class`, except for the default access specifier. Whatever you can do in a `class`, you can do in a `struct`. So these "changes" you made basically do not amount to anything. – PaulMcKenzie Jul 03 '22 at 20:04
  • @PaulMcKenzie I do know this, I just thought it is generally accepted that struct will be all public without methods, while classes will have private fields with getters, setters and other methods – Samo Chreno Jul 03 '22 at 20:09
  • 1
    [std::less](https://en.cppreference.com/w/cpp/utility/functional/less). That is an example of `struct` with a member function. So it isn't necessarily "generally accepted". – PaulMcKenzie Jul 03 '22 at 20:12
  • 1
    I fear this is *opinion based*. However, when making those design decision you should aim to make difficult to misuse your class and a terse and obvious interface. – MatG Jul 03 '22 at 20:16
  • 1
    @SamoChreno *"I just thought it is generally accepted that struct will be a [...]"* -- that is a convention, not "generally accepted". If you are in a context where that convention applies, then "switch from `struct` to `class`" means something. However, this is not one of those contexts. Better to be more direct about what you are doing -- you are not switching from `struct` to `class` so much as making the data members private. – JaMiT Jul 03 '22 at 20:17
  • Looks borderline opinion-based to me, somewhere between starting a discussion and asking for pros and cons. One might also question the focus, given that it relates to all three of the following: [Public Data members vs Getters, Setters](https://stackoverflow.com/questions/2977007/) and [When should you use 'friend' in C++?](https://stackoverflow.com/questions/17434/) and [We can directly assign value to any data member. why the use of constructor?](https://stackoverflow.com/questions/57746334/) – JaMiT Jul 03 '22 at 20:27

1 Answers1

1

A class is supposed to hold an invariant. Unless all combination of all field values are correct, 2nd version is strongly discouraged; 3rd is recommended. It's also the way to go for immutable structures which help debugging and testing very much.

lorro
  • 10,687
  • 23
  • 36
  • I'm not sure if OP gave all the info to recommend one over the other. The third may, in some contexts, be less obvious than the first. And immutable structures have limited usage (can't be moved, and usually create problems with data containers) – MatG Jul 03 '22 at 20:55
  • @MatG : an immutable structure with a single mutable bit is often used for resources where you can move from them. Erasing the bit (that's set in ctor) means the dtor of the object is no longer responsible for deallocation. – lorro Jul 03 '22 at 21:12
  • @lorro But surely you mutated a bunch of other stuff when you moved from it. – Paul Sanders Jul 03 '22 at 21:30
  • @PaulSanders not necessarily. (I actually worked with immutable systems). When you move from it, you can simply 'release' and keep it as-is, except for the owning bit (`mutable bool owning = true); // set this to false when moved from`). Then you do an `if (owning) { /* actual dtor */ }` in the dtor, i.e., release only when you haven't yet moved from it. – lorro Jul 03 '22 at 21:41
  • @lorro Oh, OK, so the moved-to-object gets a shallow copy of all the data members, essentially, but now it owns them. Sounds a tad dangerous if the moved-to object is destroyed before the moved-from object. – Paul Sanders Jul 03 '22 at 22:04
  • @PaulSanders Yes, it comes with a coding guideline that you only move from such a resource class instance if you can guarantee that the old instance goes out of scope soon (sooner than you release (return or otherwise give up control on lifetime of) the new instance). It makes sense because it's way more rare to desire to read a valid-but-unspecified value than to need _very_ quick moves in functional codes (where you're essentially rebuilding instances at each modification). – lorro Jul 03 '22 at 22:26
  • @lorro Makes sense, moved-from objects don't usually hang around very long. – Paul Sanders Jul 04 '22 at 00:22