-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature request] return object storage by value #9
Comments
Returning an object storage, in general, implies making a copy of the object. Java doesn't support that. Factory methods should make a heap allocation: public static Length# Create(double value, LengthUnit unit) {
Length# length = new Length();
length.Set(value, unit);
return length;
} Assigning object storages is not legal - I need to add an error message. |
If I understand correctly, you are asking for moving objects: static Length() Create()
{
Length() localObj;
return localObj; // OK because localObj is about to be destroyed
}
Length() objStorage = Length.Create(); // OK
objStorage = Length.Create(); // should it be legal? This seems possible. Advantages:
Drawbacks:
|
Right that's what I'm asking for... |
Also in Java doing a shallow copy is pretty trivial at least, the object just needs to implement |
If the goal is to make every language output as idiomatic as possible though, it would be nice to have some way of writing an immutable object pattern that's idiomatic in every output language. Here's the C++ header I wrote for this object when I was experimenting with using SWIG to use the C++ code from other langs...an equivalent immutable class in Java/JS/Python etc can be passed around by reference without any problems, no need to copy it. I guess if there were a specific way to declare a Ć class immutable, you could avoid the need for copying in Java etc. #ifndef UNITIZED_LENGTH_H
#define UNITIZED_LENGTH_H
#include "angle.h"
namespace unitized {
class Length
{
public:
enum Unit {
Meters = 1,
Centimeters = 2,
Kilometers = 3,
Feet = 4,
Yards = 5,
Inches = 6,
Miles = 7
};
Length(double value, Unit unit);
static Length meters(double value);
static Length centimeters(double value);
static Length kilometers(double value);
static Length feet(double value);
static Length yards(double value);
static Length inches(double value);
static Length miles(double value);
static Angle atan2(Length y, Length x);
double convertTo(Unit unit) const;
double toMeters() const;
double toCentimeters() const;
double toKilometers() const;
double toFeet() const;
double toYards() const;
double toInches() const;
double toMiles() const;
Length as(Unit unit) const;
Length asMeters() const;
Length asCentimeters() const;
Length asKilometers() const;
Length asFeet() const;
Length asYards() const;
Length asInches() const;
Length asMiles() const;
Length add(Length addend) const;
Length sub(Length subtrahend) const;
Length mul(double multiplicand) const;
Length div(double denominator) const;
double divUnitless(Length denominator) const;
Length mod(Length modulus) const;
Length abs() const;
Length negate() const;
bool isFinite() const;
bool isInfinite() const;
bool isNaN() const;
bool isNegative() const;
bool isPositive() const;
bool isZero() const;
bool isNonzero() const;
bool equals(Length other) const;
int compareTo(Length other) const;
const Unit unit;
private:
const double value;
static double convert(double value, Unit from, Unit to);
static double fromBase(double value, Unit to);
static double toBase(double value, Unit from);
};
} // namespace unitized
#endif // UNITIZED_LENGTH_H |
Explicit structures become part of the library interface. |
I see. Is that not the case for adding/removing fields to C++ classes? |
To get around the adding / removing fields for a DLL, you have to make an internal data pointer inside of the class. Here's a good example of the DLL memory alignment problem and the solution in Qt library for c++: The data is still allocated on the heap, but the accessing class can be passed by value. The underlying D-Pointer is deep copied. Or if you're fancy, you can copy on write like https://doc.qt.io/qt-5/qshareddatapointer.html |
I guess there's no way around the problem if you want pure stack allocation? |
Yea, not really. It's only really useful for types that you know won't ever change. If you change the type, the dependent applications need to rebuild. |
Yes, there's inconsistency between the generated C and C++ code:
What do you think? Should |
I do like how clean the syntax is with a lot of Qt classes since they use Pimpl; it's nice not to have to declare variables as shared pointer types. Though for this particular use case I'm going for, heap allocation feels like a waste (and I wouldn't expect to change the fields of this particular class so I wouldn't have an issue with DLLs either). But I know there are a lot of concerns to balance here... |
Is there not any kind of compiler/linker flag to automatically generate Pimpl for classes that will be exported in a DLL? Seems like something that doesn't need to be done in userland... |
That or, I'm surprised dynamic linking wasn't designed to read the sizes of exported classes from the DLL. Why hardcode the sizes of the version you compiled against, is it for performance's sake? |
No. I only found:
My experience with dynamic linking C++ code is that all the entry points are class Foo
{
public:
virtual ~Foo() = 0;
virtual void doBar() = 0;
};
extern "C" Foo *allocateFoo(void); I think nothing stops |
Back to the original problem. The argument of best performance in C and C++ is important. I think I can tolerate the two drawbacks mentioned above. Consider: class Vector3D
{
float X;
float Y;
float Z;
// named constructor
static Vector3D() Create(float x, float y, float z)
{
Vector3D() result;
result.X = x;
result.Y = y;
result.Z = z;
return result;
}
Vector3D() Add(Vector3D other)
{
return Create(X + other.X, Y + other.Y, Z + other.Z);
}
static void Demo()
{
Vector3D() a = Create(1, 2, 3);
Vector3D() b = Create(5, 10, 15);
Vector3D() c = a.Add(b);
// c = a; inconsistent if references to c existed before
Vector3D() d = b; // valid if b won't be used afterwards
}
} Care must be taken so that semantics stays same in all backends. I wouldn't like objects copied in some languages but only references copied in the other. Generally, if the source is storage that is not used afterwards, it is moved, so should be accepted as an initializer or a return value. Now for the price paid, aka disadvantages:
|
Users messing with fields directly isn't a problem for |
Added initial implementation. It has no diagnostics to prevent you from accidentally making a copy, so use with care! There are rough edges for the C target:
|
Fixed last week. See https://github.com/pfusik/ray-ci for a small sample. It exposed a new problem: if you pass objects by value, you might want to eventually assign them to a field. Which is troublesome in Ć, because it's not obvious how to implement object assignments in most of the languages. C and C++ support it out of the box. The others don't. I see two options:
For
Either way, reassignable object storage is emitted in C# and Java without the initial value or the This is still work-in-progress (e.g. the spec is not updated yet). |
Ah cool! I do hope to eventually get back to working on Cito stuff 😅 I guess I was assuming a return by value in Cito could be converted to a return by value in C/C++, but converted to a return by reference in other languages. But maybe that would cause problems? In any case this relates to how I wish there were an immutable class type in Cito. An immutable class could be passed around by value in C/C++ and by reference in other languages without risks (assuming the dev abides by the immutable paradigm in their own code in the consuming language) |
This is how I implemented it.
Immutable class type, interesting! That should work. So far I thought about C++-like move semantics. |
After what I described in #8, I thought maybe it would be possible to return an object storage:
But it causes this compiler error, which seems contradictory:
However cito supports assigning one object storage to another:
Would it be possible for cito to support this by returning by value in C++?
And return a reference in other languages like JS?
I could make the C++ API much more convenient if I could have these static methods that return by value instead of returning shared pointers, or constructing and then initializing.
The text was updated successfully, but these errors were encountered: