0

In a large C++ project, I'm changing a struct to a class, but trying to minimise changes to any code that use the struct to make it easy to revert or reapply the change.

The struct is declared as follows:

struct tParsing {
    char* elements[23];
};

And here's the current version of the class declaration (note I've shown the elements method body in the declaration for clarity, but the real code has that separately in a CPP file):

class tParsing
{
public:
    tParsing();
    ~tParsing();

    void clear();
    char* elements(int index) {
        if (index < 0 || index > 22) return NULL;
        return fElements[index];
    };

private:
    char* fElements[23];
};

Other parts of the code have many cases like the following to get one element from the struct:
parsingInstance->elements[0]

To meet my goal of minimising changes, is there any way to make the class so that the elements method can be called using array notation (instead of parentheses) to pass the index argument, so code like the line above will work regardless of whether tParsing is a struct or a class?

Scott Leis
  • 2,810
  • 6
  • 28
  • 42
  • 5
    Overload `operator[]`? – Nate Eldredge Oct 15 '21 at 05:31
  • Does this answer your question? [What is Proxy Class in C++](https://stackoverflow.com/questions/994488/what-is-proxy-class-in-c) – Joseph Sible-Reinstate Monica Oct 15 '21 at 05:32
  • Have you considered overloading `operstor[]` instead, so that you could write `parsingInstance[0]`? Yes, you'll have to change the code, but isn't it more readable/simple? – Bob__ Oct 15 '21 at 05:32
  • 1
    @Bob__ The whole point is that there's already existing code that does `parsingInstance->elements[0]`. The question is about how to be able to keep that code working without changes. – Joseph Sible-Reinstate Monica Oct 15 '21 at 05:34
  • Does this answer your question? [What are the basic rules and idioms for operator overloading?](https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading) – Karl Knechtel Oct 15 '21 at 05:38
  • @Joseph Yes, but the OP seems to be refactoring some C-style code where the users of this object know too much about its implementation, adding incapsulation. IMHO, some changes would be necessary, if not beneficial. – Bob__ Oct 15 '21 at 05:46
  • @Bob__ I agree that using `operator[]` would be better in the long run, but I really want to minimise short-term changes, at least until I've thoroughly tested the change from struct to class and am happy that the new code is reliable. – Scott Leis Oct 15 '21 at 05:50

3 Answers3

3

Simply introducing an operator[] in tParsing will break existing code like parsingInstance->elements[0] – but what if the member provides this operator?

class tParsing
{
    class Elements
    {
    public:
        char*& operator[](size_t index);
        char const* operator[](size_t index) const;
    private:
        char* data[23];
    };
public:
    Elements elements;
};

Now Elements class will manage the array and you retain compatibility with existing code. You might deprecate the operator so that new code is pushed towards new API (if planned, then you'd have an additional operator[] inside tParsing forwarding to Elements::operator[]).

Depending on your needs you might keep the further interface of Elements private and declare tParsing a friend of to allow all other access to the array via the latter class only.

Then some day, when you expect all instance->elements[...] calls having been eliminated, you can remove the nested class again and leave data management to tParsing directly.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • This looks promising. I had no idea that nested classes were a thing in C++, but I'll try this. – Scott Leis Oct 15 '21 at 05:57
  • @ScottLeis it's a declaration . You _can_ do declarations within a class , obviously. Also, Class Scope and Unqualified Name Lookup talks about nested classes. – Swift - Friday Pie Oct 15 '21 at 06:19
1

It's kind of an ugly hack, but you can keep fElements by making an inner class wrapper that overloads operator[] to access fElements, e.g. (with some test code to verify it works):

#include <iostream>


class tParsing
{
public:
    tParsing() : fElements{ new char[4]{'a', 'b', 'c', '\0'} }, elements(fElements) {}
    ~tParsing();

    void clear();

private:
    char* fElements[23];
    class elements_proxy
    {
    public:
        elements_proxy(char* elements[23]) : elements(elements) {}

        char* operator[](int index) {
            if (index < 0 || index > 22) return NULL;
            return elements[index];
        }

    private:
        char** elements;
    };


public:
    elements_proxy elements;
};

int main(int argc, char **argv) {
    auto parsingInstance = new tParsing{};

    std::cout << parsingInstance->elements[0] << std::endl;  // Outputs abc
}

Try it online!

I'm sure there are ways to simplify this (my advanced C++ is rusty), so I welcome any suggestions in the comments (the memory leaks are intentional, just trying to minimize the work to do something useful matching the OP's spec, without implementing actual parsing).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • copy (constructor/assignment) would be problematic... – Jarod42 Oct 15 '21 at 07:55
  • @Jarod42: As I note, my C++ is rusty right now, so I'm not confident on making the tweak to fix that without breaking things. If it's not too much trouble, I'd welcome an assist. – ShadowRanger Oct 15 '21 at 17:33
1

Hope this helps :) See it live here.

#include <cstddef>
#include <array>
#include <iostream>

class tParsing {
public:
  tParsing(): elements{this}, fElements{} {}
  tParsing(const tParsing& other): elements{this}, fElements(other.fElements) {}
  tParsing(tParsing&& other): tParsing(other) {}
  tParsing& operator=(const tParsing& other) {
    fElements = other.fElements;
    return *this;
  }
  tParsing& operator=(tParsing&& other) { return operator=(other); }
    
  struct proxy_t {
    tParsing* p;
    char*& operator[](std::size_t index) { return p->fElements[index]; }  
    char* const & operator[](std::size_t index) const { return p->fElements[index]; }
  } elements;
  
private:
  std::array<char*, 23> fElements;
};
  
int main() {
  auto p = new tParsing;
  p->elements[0] = new char('x');
  std::cout << *p->elements[0] << std::endl;
}
Lingxi
  • 14,579
  • 2
  • 37
  • 93