Problem with copy constructor (a bit long story...)

J

Jeroen

Hi all,

I'm trying to implement a certain class but I have problems regarding
the copy ctor. I'll try to explain this as good as possible and show
what I tried thusfar. Because it's not about a certain code syntax but
more a 'code architecture' thing , I'll use simple example classes
(which are certainly not complete or working...) just to illustrate the
idea (and I may make some mistakes because I'm not that experienced...).

The basic idea is that I have a class which has a data vector, but
objects of that class can also share that data vector between several
instances:

class A {
vector<double> *data; // pointer, so objects of 'A' can _share_ data.
bool data_is_shared; // does this 'A' share data with another A?

A(const bool do_data_init=true); // ctor
A(const A& a); // copy constructor
A get_shared_A(); // get an object A which shares 'this' data
};

Ctor:

A::A(bool do_data_init)
{
if (do_data_init)
data = new vector<double>;
data_is_shared = !do_data_init;
}

Copy ctor, always result in an 'A' which does not share data:

A::A(const A& a)
{
data = new vector<double>;
data_is_shared = false;
*data = *a.data;
}

Get an object A which shares data with 'this':

A A::get_shared_A()
{
A a(false);

a.data = data; // copy the data pointer only
return a;
}

The problems focus on this last method 'get_shared_A'. It should return
an object A that shares its data with 'this'. Problem: on returning 'a',
the copy constructor _may_ be called and then an object A is returned
which has its own data vector. So this doesn't work... I cannot change
the copy ctor because that should always return an A which does not
share its data (that's a requirement for correct behaviour of class A).

In my application, 'get_shared_A' is an operator which selects a part of
the original data in 'this', and the returned result must be an object A
because it must be used in mathematical expressions just like all other A's.

So, using a 'helper'-class B I tried to change this in:

A A::get_shared_A()
{
B b;

b.data = data; // copy the data pointer only
return b;
}

With class B:

class B {
vector<double> *data;
};

And an extra conversion ctor for A:

A::A(const& B b)
{
data = b.data;
data_is_shared = true;
}

So I wanted the code to force to use this new ctor and return an object
A which shares its data. But again, after the conversion ctor is called
it is still possible that the copy ctor is also called and things do not
work correctly.

In fact, all possible solutions I came up with do not work because you
cannot be sure if the copy ctor is called when returning from
'get_shared_A', it depends on the compiler you have.

So, does anybody has any ideas to overcome this problem?

Jeroen
 
A

Alf P. Steinbach

* Jeroen:
The basic idea is that I have a class which has a data vector, but
objects of that class can also share that data vector between several
instances:

Use boost::shared_ptr to implement the sharing. If you want a
non-shared copy provide that via some member function. Not via the copy
constructor.
 
J

Jeroen

Alf P. Steinbach schreef:
* Jeroen:

Use boost::shared_ptr to implement the sharing. If you want a
non-shared copy provide that via some member function. Not via the copy
constructor.

That may just be the problem. The library in which class A resides uses
the concept of shared data and so on. But for the user all of this is
not available, nor visible. The user expects 'normal' behaviour of class
A. So if a user of the library uses a subscript operator in order to
select a portion of the data in an object A and directly uses that as a
parameter to a function, then the copy constructor is called which is
required to make a non data-sharing copy:

void user_function(A a)
{
a = 10; // parameter 'a' must be changed here, not the original data!
}

void other_user_function()
{
A a;
user_function(a("1:10")); // or whatever syntax I'l use here....
}

The code 'a("1:10")' results in an object of class A which shares its
data with its 'parent' (this was coded in my original post by method
'get_shared_A' in class A, but its an operator in fact), but the copy
constructor called for 'user_function' must create an A which does not
share its data in order to provide correct behaviour.

Given the above, I don't see the solution in your suggestion...

Jeroen
 
A

Alf P. Steinbach

* Jeroen:
Alf P. Steinbach schreef:

That may just be the problem. The library in which class A resides uses
the concept of shared data and so on. But for the user all of this is
not available, nor visible. The user expects 'normal' behaviour of class
A. So if a user of the library uses a subscript operator in order to
select a portion of the data in an object A and directly uses that as a
parameter to a function, then the copy constructor is called which is
required to make a non data-sharing copy:

void user_function(A a)
{
a = 10; // parameter 'a' must be changed here, not the original data!
}

void other_user_function()
{
A a;
user_function(a("1:10")); // or whatever syntax I'l use here....
}

The code 'a("1:10")' results in an object of class A which shares its
data with its 'parent' (this was coded in my original post by method
'get_shared_A' in class A, but its an operator in fact), but the copy
constructor called for 'user_function' must create an A which does not
share its data in order to provide correct behaviour.

Given the above, I don't see the solution in your suggestion...

Not to put to fine words on it, you're confused. You want a copy
constructor that has two incompatible properties: making a non-shared
copy (only), and making a shared copy (only). If the sharing, as you
write above, is an implementation detail, then try to define exactly
what it's meant to accomplish; e.g. it may be that what you're trying to
achieve is some copy-on-write scheme, in which case do as I wrote.
 
J

Jeroen

Alf P. Steinbach schreef:
* Jeroen:

Not to put to fine words on it, you're confused. You want a copy
constructor that has two incompatible properties: making a non-shared
copy (only), and making a shared copy (only). If the sharing, as you
write above, is an implementation detail, then try to define exactly
what it's meant to accomplish; e.g. it may be that what you're trying to
achieve is some copy-on-write scheme, in which case do as I wrote.

As I think things over, I see more problems on the horizon :-( But I
will certainly take a good look on boost::shared_ptr. Thanks for replying !
 
?

=?ISO-8859-1?Q?Erik_Wikstr=F6m?=

Hi all,

I'm trying to implement a certain class but I have problems regarding
the copy ctor. I'll try to explain this as good as possible and show
what I tried thusfar. Because it's not about a certain code syntax but
more a 'code architecture' thing , I'll use simple example classes
(which are certainly not complete or working...) just to illustrate the
idea (and I may make some mistakes because I'm not that experienced...).

The basic idea is that I have a class which has a data vector, but
objects of that class can also share that data vector between several
instances:

class A {
vector<double> *data; // pointer, so objects of 'A' can _share_ data.
bool data_is_shared; // does this 'A' share data with another A?

A(const bool do_data_init=true); // ctor
A(const A& a); // copy constructor
A get_shared_A(); // get an object A which shares 'this' data
};

Ctor:

A::A(bool do_data_init)
{
if (do_data_init)
data = new vector<double>;
data_is_shared = !do_data_init;
}

Copy ctor, always result in an 'A' which does not share data:

A::A(const A& a)
{
data = new vector<double>;
data_is_shared = false;
*data = *a.data;
}

Get an object A which shares data with 'this':

A A::get_shared_A()
{
A a(false);

a.data = data; // copy the data pointer only
return a;
}

The problems focus on this last method 'get_shared_A'. It should return
an object A that shares its data with 'this'. Problem: on returning 'a',
the copy constructor _may_ be called and then an object A is returned
which has its own data vector. So this doesn't work... I cannot change
the copy ctor because that should always return an A which does not
share its data (that's a requirement for correct behaviour of class A).

In my application, 'get_shared_A' is an operator which selects a part of
the original data in 'this', and the returned result must be an object A
because it must be used in mathematical expressions just like all other A's.

So, using a 'helper'-class B I tried to change this in:

A A::get_shared_A()
{
B b;

b.data = data; // copy the data pointer only
return b;
}

With class B:

class B {
vector<double> *data;
};

And an extra conversion ctor for A:

A::A(const& B b)
{
data = b.data;
data_is_shared = true;
}

So I wanted the code to force to use this new ctor and return an object
A which shares its data. But again, after the conversion ctor is called
it is still possible that the copy ctor is also called and things do not
work correctly.

In fact, all possible solutions I came up with do not work because you
cannot be sure if the copy ctor is called when returning from
'get_shared_A', it depends on the compiler you have.

So, does anybody has any ideas to overcome this problem?

Some time a go (Qt3) I took a look at the implicit sharing mechanism of
QList, the idea is that if you do something like this:

QList a;
/* add elements to a */
QList b = a;

then b will share the elements with a until one of them makes any
changes to the list, at which point it will "detach" (which basically
means that it will create a copy of the list and make the changes to the
copy).

If I understood your problem correctly it's quite similar but the other
way around, you want explicit sharing, but the idea should still be the
same and if you understand how they did it in Qt you should be able to
make it work for you (unless you need it to be thread-safe, at which
point it becomes a bit more tricky).
 
M

MathWizard

Erik said:
Some time a go (Qt3) I took a look at the implicit sharing mechanism
of QList, the idea is that if you do something like this:

QList a;
/* add elements to a */
QList b = a;

then b will share the elements with a until one of them makes any
changes to the list, at which point it will "detach" (which basically
means that it will create a copy of the list and make the changes to
the copy).

If I understood your problem correctly it's quite similar but the
other way around, you want explicit sharing, but the idea should still
be the same and if you understand how they did it in Qt you should be
able to make it work for you (unless you need it to be thread-safe, at
which point it becomes a bit more tricky).
OK, thanks for the pointer :) The problem that I came up with is a
little more 'sophisticated' than just sharing data. I try to implement a
matrix class which supports subscripting with a string, thus enabling to
'select' submatrices. For example:

matrix m(10,10); // 10x10 matrix
m("1:3,:") = 1; // rows 1 to 3 become '1'.

In this example, I use operator () for smart subscripting and this
operator must return a matrix object which only points to the selected
data range of its 'parent' matrix 'm' so it can be modified. Looks like
sharing data, but in this case I want the 'shared data' to be changed!

On the other hand, if you have a function (useless code by the way, but
just to illustrate things):

void my_function(matrix m)
{
m = 1;
}

and you call that by:

my_function(m("1:4,:"));

then my subscripting operator returns a matrix object which only points
to the selected data (this behaviour follows from the previous example),
but the copy constructor called when calling my_function must return a
matrix object which has its own data in order to prevent that the
original data is changed within my_function. But the copy ctor may
interfere within my operator () as I illustrated in my original post.

I'm at the point where I need to figure out all the functionality
required by correct matrix behaviour (the only thing I can come up with
is to generate as many possible situations like the previous examples),
and check if there isn't some inherent impossibility to make things
work.... Maybe this project is just too difficult for me :)

Jeroen
 
?

=?ISO-8859-1?Q?Erik_Wikstr=F6m?=

OK, thanks for the pointer :) The problem that I came up with is a
little more 'sophisticated' than just sharing data. I try to implement a
matrix class which supports subscripting with a string, thus enabling to
'select' submatrices. For example:

matrix m(10,10); // 10x10 matrix
m("1:3,:") = 1; // rows 1 to 3 become '1'.

In this example, I use operator () for smart subscripting and this
operator must return a matrix object which only points to the selected
data range of its 'parent' matrix 'm' so it can be modified. Looks like
sharing data, but in this case I want the 'shared data' to be changed!

On the other hand, if you have a function (useless code by the way, but
just to illustrate things):

void my_function(matrix m)
{
m = 1;
}

and you call that by:

my_function(m("1:4,:"));

then my subscripting operator returns a matrix object which only points
to the selected data (this behaviour follows from the previous example),
but the copy constructor called when calling my_function must return a
matrix object which has its own data in order to prevent that the
original data is changed within my_function. But the copy ctor may
interfere within my operator () as I illustrated in my original post.

I'm at the point where I need to figure out all the functionality
required by correct matrix behaviour (the only thing I can come up with
is to generate as many possible situations like the previous examples),
and check if there isn't some inherent impossibility to make things
work.... Maybe this project is just too difficult for me :)

Sounds a bit like valarray and slice/slice_array, might be worth taking
a look at, if nothing else as an inspiration.
 
M

Mumia W.

[...]
On the other hand, if you have a function (useless code by the way, but
just to illustrate things):

void my_function(matrix m)
{
m = 1;
}

and you call that by:

my_function(m("1:4,:"));

then my subscripting operator returns a matrix object which only points
to the selected data (this behaviour follows from the previous example),
but the copy constructor called when calling my_function must return a
matrix object which has its own data in order to prevent that the
original data is changed within my_function. But the copy ctor may
interfere within my operator () as I illustrated in my original post.
[...]

You might be able to eliminate the copy-constructor call by making
my_function accept a reference:

void my_function (matrix & m) { ... }
 
J

Jeroen

Mumia W. schreef:
[...]
On the other hand, if you have a function (useless code by the way,
but just to illustrate things):

void my_function(matrix m)
{
m = 1;
}

and you call that by:

my_function(m("1:4,:"));

then my subscripting operator returns a matrix object which only
points to the selected data (this behaviour follows from the previous
example), but the copy constructor called when calling my_function
must return a matrix object which has its own data in order to prevent
that the original data is changed within my_function. But the copy
ctor may interfere within my operator () as I illustrated in my
original post.
[...]

You might be able to eliminate the copy-constructor call by making
my_function accept a reference:

void my_function (matrix & m) { ... }

That's not the problem because this works fine if I have a copy
constructor which always returns a non-sharing matrix object. The
problem lies within the operator () which I use for subscripting.
Because I do not know if a copy constructor is called on return (depends
on compiler), correct behaviour of my implementation is not guaranteed.

What I want is to differentiate between situations where a copy ctor is
called:

* if the copy ctor is called when calling a function (pass by value
parms), then a non-sharing matrix object must be created.
* if the copy ctor is called when returning a matrix object from a
function, then an exact copy must be made (sharing or non-sharing
characteristics are not changed by the copy).

That's seems to be impossible, so I think I have to come up with some
other mechanism to get it right....
 
M

Mumia W.

[...]
* if the copy ctor is called when calling a function (pass by value
parms), then a non-sharing matrix object must be created.
* if the copy ctor is called when returning a matrix object from a
function, then an exact copy must be made (sharing or non-sharing
characteristics are not changed by the copy).

That's seems to be impossible, so I think I have to come up with some
other mechanism to get it right....

I think you can get closer to your objective if you're willing to return
a pointer from get_shared_A [or the overloaded operator()] rather than
an object; however, that introduces the complexities of dealing with
pointers.

Another option might be to maintain a list of sharable objects. A
modified copy constructor would find out what to do by testing if the
object it's copying is shareable:


#include <cstdio>
#include <cstdlib>
#include <cstring>

struct Vector;

struct Vector {
int len;
int * data;

static const int maxv = 100;
static int svpos;
static Vector * shareable [maxv];

Vector(int elen, int * edata) : len(elen), data(edata) { }
Vector(const Vector & obj) {
len = obj.len;
if (! obj.is_shareable()) {
data = new int[len];
memcpy(data,obj.data,len*sizeof(*data));
} else {
data = obj.data;
make_shareable();
}
}
~Vector() {
delete[] data;
len = 0; data = 0;
}
Vector get_shared() const {
Vector another(len,data);
another.make_shareable();
return another;
}
void make_shareable () {
if (svpos >= maxv) { exit(1); }
shareable[svpos++] = this;
}
bool is_shareable() const {
for (int nx = 0; nx < svpos; ++nx) {
if (shareable[nx] == this) { return true; }
}
return false;
}
void print (FILE * fid) const {
for (int nx = 0; nx < len; nx++) {
printf("%d ", data[nx]);
}
puts("");
}
};

int Vector::svpos = 0;
Vector * Vector::shareable [Vector::maxv];

int main ()
{
int itinit [] = { 6, 15, 76, 41, 91, 37, 42 };
int itsize = sizeof(itinit)/sizeof(itinit[0]);

Vector first(itsize, itinit);
Vector second = Vector(first.get_shared());

first.data[1] = 7000;
first.data[2] = 7020;

first.print(stdout);
second.print(stdout);

return EXIT_SUCCESS;
}

----------end-code--------

As you can see, I'm more of a C than a C++ programmer right now. :)

The magic is in the copy-constructor which calls is_shareable() on the
other object before performing the copy. The list of shared objects, is
maintained as class data in this program.

My compiler, gcc, doesn't invoke the copy-ctor when I assign from
get_shared(), so I had to force it to use the copy constructor just to
be sure that the technique worked correctly.

I consider this somewhat of a kludge because the copy-ctor doesn't
always produce an independent copy. Users have to be aware that
make_shareable() might have been called on some of their objects :-(

I didn't write a make_nonshareable() for this program, but it would be
trivial to write; the primary problem is that users might not know to
call the function before attempting to make independent copies.
 
J

Jeroen

Mumia W. schreef:
[...]
* if the copy ctor is called when calling a function (pass by value
parms), then a non-sharing matrix object must be created.
* if the copy ctor is called when returning a matrix object from a
function, then an exact copy must be made (sharing or non-sharing
characteristics are not changed by the copy).

That's seems to be impossible, so I think I have to come up with some
other mechanism to get it right....

I think you can get closer to your objective if you're willing to return
a pointer from get_shared_A [or the overloaded operator()] rather than
an object; however, that introduces the complexities of dealing with
pointers.

Another option might be to maintain a list of sharable objects. A
modified copy constructor would find out what to do by testing if the
object it's copying is shareable:


#include <cstdio>
#include <cstdlib>
#include <cstring>

struct Vector;

struct Vector {
int len;
int * data;

static const int maxv = 100;
static int svpos;
static Vector * shareable [maxv];

Vector(int elen, int * edata) : len(elen), data(edata) { }
Vector(const Vector & obj) {
len = obj.len;
if (! obj.is_shareable()) {
data = new int[len];
memcpy(data,obj.data,len*sizeof(*data));
} else {
data = obj.data;
make_shareable();
}
}
~Vector() {
delete[] data;
len = 0; data = 0;
}
Vector get_shared() const {
Vector another(len,data);
another.make_shareable();
return another;
}
void make_shareable () {
if (svpos >= maxv) { exit(1); }
shareable[svpos++] = this;
}
bool is_shareable() const {
for (int nx = 0; nx < svpos; ++nx) {
if (shareable[nx] == this) { return true; }
}
return false;
}
void print (FILE * fid) const {
for (int nx = 0; nx < len; nx++) {
printf("%d ", data[nx]);
}
puts("");
}
};

int Vector::svpos = 0;
Vector * Vector::shareable [Vector::maxv];

int main ()
{
int itinit [] = { 6, 15, 76, 41, 91, 37, 42 };
int itsize = sizeof(itinit)/sizeof(itinit[0]);

Vector first(itsize, itinit);
Vector second = Vector(first.get_shared());

first.data[1] = 7000;
first.data[2] = 7020;

first.print(stdout);
second.print(stdout);

return EXIT_SUCCESS;
}

----------end-code--------

As you can see, I'm more of a C than a C++ programmer right now. :)

The magic is in the copy-constructor which calls is_shareable() on the
other object before performing the copy. The list of shared objects, is
maintained as class data in this program.

My compiler, gcc, doesn't invoke the copy-ctor when I assign from
get_shared(), so I had to force it to use the copy constructor just to
be sure that the technique worked correctly.

I consider this somewhat of a kludge because the copy-ctor doesn't
always produce an independent copy. Users have to be aware that
make_shareable() might have been called on some of their objects :-(

I didn't write a make_nonshareable() for this program, but it would be
trivial to write; the primary problem is that users might not know to
call the function before attempting to make independent copies.

I'll take a close look at it :) Your observation however that "Users
have to be aware that make_shareable() might have been called on some of
their objects :-( " does not klook promising though. I try to find an
architecture which behaviour is correct in all cases... Fool-proof :)

Thanks,

Jeroen
 
J

James Kanze

I'm trying to implement a certain class but I have problems regarding
the copy ctor. I'll try to explain this as good as possible and show
what I tried thusfar. Because it's not about a certain code syntax but
more a 'code architecture' thing , I'll use simple example classes
(which are certainly not complete or working...) just to illustrate the
idea (and I may make some mistakes because I'm not that experienced...).
The basic idea is that I have a class which has a data vector, but
objects of that class can also share that data vector between several
instances:
class A {
vector<double> *data; // pointer, so objects of 'A' can _share_ data.
bool data_is_shared; // does this 'A' share data with another A?
A(const bool do_data_init=true); // ctor
A(const A& a); // copy constructor
A get_shared_A(); // get an object A which shares 'this' data
};

A::A(bool do_data_init)
{
if (do_data_init)
data = new vector<double>;
data_is_shared = !do_data_init;

Danger: what is data if ! do_data_init?

Why not:

A::A( bool do_data_init )
: data( do_data_init ? new vector< double > : NULL )
, data_is_shared( ! do_data_init )
{
}
Copy ctor, always result in an 'A' which does not share data:
A::A(const A& a)
{
data = new vector<double>;
data_is_shared = false;
*data = *a.data;
}

Why not:

A::A( A const& other )
: data( new vector<double>( *other.data )
, data_is_shared( false )
{
}

Except: hwat happens if other.data == NULL (or hasn't been
initialized, in your code)?
Get an object A which shares data with 'this':
A A::get_shared_A()
{
A a(false);
a.data = data; // copy the data pointer only
return a;
}

This won't work. The copy constructor may be used to return the
value, so you'll end up with a deep copy anyway. Sometimes,
since it's pretty much up to the compiler whether the copy
constructor is called or not.
The problems focus on this last method 'get_shared_A'. It should return
an object A that shares its data with 'this'. Problem: on returning 'a',
the copy constructor _may_ be called and then an object A is returned
which has its own data vector. So this doesn't work... I cannot change
the copy ctor because that should always return an A which does not
share its data (that's a requirement for correct behaviour of class A).

IMHO: the real solution lies in making the copy constructor
respect the sharing. Something along the lines of:

A::A( A const& other )
: data( other.data_is_shared
? other.data
: new vector< double >( *other.data ) )
, data_is_shared( other.data_is_shared )
{
}

But I still don't really like it. You have a single class,
whose semantics change from reference to copy according to an
attribute. Generally speaking, you want to treat classes with
reference semantics considerably differently than classes with
value semantics.
In my application, 'get_shared_A' is an operator which selects
a part of the original data in 'this', and the returned result
must be an object A because it must be used in mathematical
expressions just like all other A's.

In which case, it almost certainly needs value semantics. If
not, you're going to get into trouble very, very quickly.

[...]
In fact, all possible solutions I came up with do not work because you
cannot be sure if the copy ctor is called when returning from
'get_shared_A', it depends on the compiler you have.

And the level of optimization, and possibly the context in which
the function is called.
So, does anybody has any ideas to overcome this problem?

What is the problem you are trying to solve?
 
J

Jeroen

James Kanze schreef:
Danger: what is data if ! do_data_init?

Why not:

A::A( bool do_data_init )
: data( do_data_init ? new vector< double > : NULL )
, data_is_shared( ! do_data_init )
{
}

It looks a bit clumsy the way I put this example code down, but only in
a single method (get_shared_A() see a few lines lateron) the parameter
do_data_init should get the value 'false'. In that method I take care of
property data. The user of the library should not know (is not
allowed) to use the construction with do_data_init=false.
Why not:

A::A( A const& other )
: data( new vector<double>( *other.data )
, data_is_shared( false )
{
}

Except: hwat happens if other.data == NULL (or hasn't been
initialized, in your code)?

Should be taken care of because get_shared_A is the only function that
is allowed to call the ctor withoud initializing 'data'.
This won't work. The copy constructor may be used to return the
value, so you'll end up with a deep copy anyway. Sometimes,
since it's pretty much up to the compiler whether the copy
constructor is called or not.

Yep, I figured it out, see below...
IMHO: the real solution lies in making the copy constructor
respect the sharing. Something along the lines of:

A::A( A const& other )
: data( other.data_is_shared
? other.data
: new vector< double >( *other.data ) )
, data_is_shared( other.data_is_shared )
{
}

But I still don't really like it. You have a single class,
whose semantics change from reference to copy according to an
attribute. Generally speaking, you want to treat classes with
reference semantics considerably differently than classes with
value semantics.

Uop till now, yes... I thought this was the way to go for my application...
In my application, 'get_shared_A' is an operator which selects
a part of the original data in 'this', and the returned result
must be an object A because it must be used in mathematical
expressions just like all other A's.

In which case, it almost certainly needs value semantics. If
not, you're going to get into trouble very, very quickly.

[...]
In fact, all possible solutions I came up with do not work because you
cannot be sure if the copy ctor is called when returning from
'get_shared_A', it depends on the compiler you have.

And the level of optimization, and possibly the context in which
the function is called.
So, does anybody has any ideas to overcome this problem?

What is the problem you are trying to solve?

I write a post under my home-account 'MathWizard' in this thread. Maybe
that gives a little more explanation of what I try to achieve. As I
mention in that post, it may be impossible to find a solution for the
things I want to implement and I have to do a lot of thinking about that.

Again, thanks for your time and effort James. It's very helpfull to me.

Jeroen
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,188
Latest member
Crypto TaxSoftware

Latest Threads

Top