Jump to content

Rudy Velthuis

Members
  • Content Count

    285
  • Joined

  • Last visited

  • Days Won

    4

Everything posted by Rudy Velthuis

  1. Rudy Velthuis

    operator overloding Equal vs Multiply

    Having dynamic array parameters in an operator declaration is fine. Having open array parameters is not, IMO. In your case, simply do: function EqualArrays(const A: TMyArray; B: array of Integer): Boolean; And don't use operators. Hmmm... I see you use == C-like syntax. How would you do this in, say, C++? In C++, for an open array, you would have to pass a third parameter for the length of the static array, but how would you pass that? I doubt that the syntax allows it. I am pretty sure you would have to use... a plain function (or perhaps some template magic, but we don't have that in Delphi).
  2. Rudy Velthuis

    operator overloding Equal vs Multiply

    Bad syntax, unless you mean to use them as default parameter. But default parameters don't match the notion of a dyadic operator either. It doesn't make a lot of sense to write: x := a +; where the second operand is a default value. In other words: there are, IMO, some things that should not be used with operators that are fine with normal functions: open array parameters and default parameters are two of them. Operators may be functions, but they have special semantics and not everything is allowed. IMO, open array parameters and default parameters should be among the features not allowed for operators either.
  3. Rudy Velthuis

    operator overloding Equal vs Multiply

    I'm not quite sure if you are confusing open array parameters and dynamic arrays. They look alike, but are not the same thing. Your TArray<Double> is not an "open array", it is a dynamic array. I meant open array parameters. IMO, it doesn't make sense to give operators open array parameters. That would be like giving them three operands, e.g.: class operator Divide(const a, b: MyFloat; rounding: MyRoundingMode); It is obvious that that doesn't fit in the scheme x := operand1 / operand2; In the same way, open array parameters do not fit in there, IMO. Dynamic arrays do, of course, but I didn't mean those. Take a look at the concatenation of dynarrays: b := b + [4, 5, 6]; but those are not open array parameters. FWIW, it is QP, not QC anymore.
  4. Rudy Velthuis

    operator overloding Equal vs Multiply

    But not open array arguments, IMO. Somehow that is against the idea of operators. It feels wrong. But that is my personal opinion.
  5. Rudy Velthuis

    ABI Changes in RAD Studio 10.3

    I would recompile the DLL, indeed, but make any parameter with 5-8 bytes explicitly a reference parameter: either var, out or const [ref], and adopt my calling code accordingly (in all versions I compile for). Then the ABI change doesn't matter. Of course that doesn't help if you want to deploy the DLL to be used by different already deployed programs written with different versions. That is a big nuisance, IMO, and I wish they hadn't done this (I'd rather they had changed their C++ compilers). But if there is a chance of recompiling, I would certainly use explicit reference passing.
  6. Rudy Velthuis

    Should my record be a class instead?

    Actually, I hardly ever have to check for the type. And having parameters within range: that depends. I usually have an unchecked and a checked version. Internally, I generally use the unchecked code (no range checks, I trust myself to pass arguments in the proper range). For code to be called by others, e.g. in the public interface of a component or library, I do checks. But even then, I usually have a way for the user to tell me if (s)he wants range and/or overflow checks, or if (s)he is confident. The user could set the options to "checking" in debug and "no checking" in release mode.
  7. Rudy Velthuis

    Time bomb

    I've seen such apparently useless constructs before. Not quite sure why they are there, but I was told (by those who can know) it may have something to do with debugging, although you can also find them in release code.
  8. Rudy Velthuis

    Should my record be a class instead?

    He has saved a lot of things: time: records don't need complicated allocation, objects do; nor do records need complicated deallocation; space: records are generally smaller than objects; hassle: usually no need to take care of their lifetime management; more hassle: he already has records; there is no need to rewrite things. So I don't see why on earth he should use objects, if he can use records. Yes, they also need initialization. So what? I don't get your objections WRT naming of constructors, or why you think that constructors should be automatic. Note that default constructors, as they were envisioned for 10.3 Rio, together with default destructors, copy constructors, conversion constructors and overloaded assignment operators have totally different purposes. They allow you to get rid of the (for the user invisible, but rather slow) initialization, finalization, etc. which is generally done by relatively slow runtime functions like _InitializeRecord, _FinalizeRecord, CopyRecord, etc. and which use RTTI (at runtime!) to search their way through a record and initialize, finalize or copy the managed fields they find. These special constructors and destructors are meant to give you the opportunity to replace these slow but automatic runtime routines by your own code, i.e. you tell the compiler: I know what I am doing, I know which members need special handling, so there is no need to call these slow routines. If you don't write these special methods, the automatic behaviour will still take place, i.e. managed fields will be initialized to 0 (or whatever the type equivalent is), refcounts will be handled, etc.
  9. Rudy Velthuis

    operator overloding Equal vs Multiply

    I fully agree. It is never a good idea to give overridden operators variant open array parameters. You should have two (or sometimes one) single operands, not arrays. If you want to pass arrays, use functions.
  10. It is never a good idea to increment the size by one. See this blog post: Extending arrays But otherwise: yes, you are being too pedantic. If this is in a tight loop, you might think of profiling which is faster, otherwise, it doesn't really matter. FWIW, I would do it like this (after Delphi XE7, IIRC): NewRecord.SomeField := SomeValue; // etc... MyArray := MyArray + [NewRecord]; No need for SetLength, Len or High
  11. Rudy Velthuis

    Should my record be a class instead?

    Yes, certainly. That is the same in programming environments with automatic lifetime management. But with automatic lifetime management, you don't have to take care of freeing them, i.e. you don't have to think about when you can free them and when not, and you can't forget to free them, nor will you free them too early. That is not only safer, it is also less of a bother. It takes a tedious and boring task off my hands.
  12. Rudy Velthuis

    Should my record be a class instead?

    Hold on turning your records into object until they introduce automatic construction and destruction in Delphi. If then, you still want to turn it into an object, then do so. Otherwise, records may be the more ligthweight choice with easier (stack based, and soon automatic) memory management, especially well suited for short-living objects.
  13. Rudy Velthuis

    Should my record be a class instead?

    All these things do not really bother me, but I am thinking of installing movement detectors so doors open (and close) automatically, and lights go on (and off again) automatically. And objects in a program can much more easily be automated than physical objects in a house or building. Add to that that light and door management is often forgotten (doors here are often open and we forget to turn off the light in the kitchen). Likewise, one can also forget to free objects. Computers are much better at this than we are and they don't forget. Manual memory management: is very often done wrong, even by those who think they fully control it; you only really control it if you can code thus that you can be sure that you can't have objects that are freed to early or too late. Not many actually know how to do that (or actually: how to avoid writing code that can do these bad things). Object freed too early: you could access a freed object. Object freed too late: memory leak. is a chore so why not get rid of it? It is not that I can't handle it, it is that I don't want to. I can also handle shift gears and yet I prefer an automatic gear, because that is more relaxed. is the cause of many (most?) errors in programming. It is always nice if you can remove such an important source of errors. So I am quite sad that they want to roll back ARC. It had its flaws, but more because the libraries were not really designed for it and had sometimes conflicting memory management strategies like the ownership/notification principle in the VCL and FMX than because of how ARC actually works. Now they seem to be thinking of going the lightweight smart pointers (hence the desire for auto-constructors and auto-destructors). I am not sure if that is a solution. I had hoped they would go the way MS proposed, a different kind of (indirect) refcounting, something I have been thinking about for years. It requires that pointers work slightly differently, and don't directly point to an object. Oh well. So again: yes we can certainly do all these things, but we tend to forget them and the fact we must do them repeatedly and often is a nuisance, so something that relieves us of that task can make our code safer and easier to write at the same time. It is like buying a car with automatic gear instead of shift gear.
  14. Rudy Velthuis

    ABI Changes in RAD Studio 10.3

    Actually, there is a difference: Win64 compiler will be slightly different. What is expected to be a 64 bit pass-by-value has become a pass-by-reference parameter now. Delphi will handle this transparently, but for assembler, there is a difference.
  15. The compiler needs a type for Bob so it can know it should return a TTalking (through the Implicit operator) and not a TSmartPtr<TTalking>. There is no way around that. But you can do without the inference: var Bob: TTalking := SmartPtr<TTalking>(TTalking.Create('Bob'));
  16. Heh, I actually donated that SafeGuard code to JCL many years ago. They modified the name a little, but not the concept. It works, but I am not sure if all guarded objects will be released on an exception. You'll have to test that. The advantage is indeed that you don't have to cast or anything. You use the original object you created. Yesterday, I even wrote a better approach, using records an implicit and explict casts. That one is certain to work and does not need a cast and the pointer can be used as if it were the actual object (by implicit cast). type IObjectGuard<T: class> = interface function Value: T; end; TObjectGuard<T: class> = class(TInterfacedObject, IObjectGuard<T>) private FValue: T; public constructor Create(Obj: T); destructor Destroy; override; function Value: T; end; SmartPtr<T: class> = record private FGuard: IObjectGuard<T>; public class operator Explicit(const Obj: T): SmartPtr<T>; class operator Implicit(const Ptr: SmartPtr<T>): T; end; { TObjectGuard<T> } constructor TObjectGuard<T>.Create(Obj: T); begin FValue := Obj; end; destructor TObjectGuard<T>.Destroy; begin FValue.Free; inherited; end; function TObjectGuard<T>.Value: T; begin Result := FValue; end; { SmartPtr<T> } class operator SmartPtr<T>.Explicit(const Obj: T): SmartPtr<T>; begin Result.FGuard := TObjectGuard<T>.Create(Obj); end; class operator SmartPtr<T>.Implicit(const Ptr: SmartPtr<T>): T; begin if Ptr.FGuard <> nil then Result := Ptr.FGuard.Value else Result := nil; end; And it can be used like: var Bob, Fred, Jack: TTalking; begin Bob := SmartPtr<TTalking>(TTalking.Create('Bob')); Fred := SmartPtr<TTalking>(TTalking.Create('Fred')); Jack := SmartPtr<TTalking>(TTalking.Create('Jack')); Fred.Talk; Bob.Talk; raise Exception.Create('Error Message'); Jack.Talk; Writeln('This is the end of the routine'); end; As you can see, this also creates guards. They do work, even with the Exception. All are freed when the exception occurs.
  17. Rudy Velthuis

    Strange Behaviour of FillChar for Non Byte Array Arrays.

    Yes. Check that 67372036 is the same as hex $04040404, in other words, 4 bytes, each with value 4. FillChar fills bytes, even if you pass a UInt32 or even a UInt64. It will only take the low byte and multiplicate that.
  18. Rudy Velthuis

    Custom Managed Records Coming in Delphi 10.3

    Automatic invocation of the (parameterless) default constructor (and a copy constructor -- with one parameter of the same type as the record) is the whole point. All other constructors should still be called explicitly, just like in C++, so no need to turn things into class functions. There should only be one destructor, and that should also automatically be called. Actually, that is the most important thing about it. Even if the constructor were explicit, you would still want an automatically called destructor.
  19. Oh, I use that too, but only in simple test programs. So it all depends on where it is used.
  20. Yes, but Join should be used before you start trimming at the end, i.e. to avoid having to trim at all.
  21. Rudy Velthuis

    Custom Managed Records Coming in Delphi 10.3

    Wha exactly do you mean with "member lifting"? I only know the term in association with nullable types.
  22. Rudy Velthuis

    Custom Managed Records Coming in Delphi 10.3

    That should be begin var s := New.Of(TStringList.Create); Note that Of could be an error (keyword), although as New.Of it might compile. You could give TAutoFreeRecord a class function returning an instance of the record: begin var S := TAutoFreeRecord.New(TStringList.Create); // returns TStringList instance
  23. Rudy Velthuis

    Custom Managed Records Coming in Delphi 10.3

    They don't intrduce automatisms you don't want. In fact, they now let you customize or even avoid the already existing automatisms (e.g. for records with managed content), allowing for better optimization. At the moment, normal, so called plain records (i.e. not M-records) are not managed. But records with managed content are already automatically managed using the usual (hidden) functions System._InitializeRecord, System_CopyRecord and System._FinalizeRecord. That management always takes place, and there (was) no way to get out of that. But these routines are slow and use TypeInfo. They must walk through that type info to determine which members must be initialized, refcounted, finalized, etc. at what moment. That takes time. With the new M-records, you can replace these routines by declaring a (default) constructor without parameters, a (default) destructor and a new Assign operator. These *replace* _InitializeRecord, _CopyRecord and _FinalizeRecord. So now you can selectively only initialize/copy/finalize what needs to be done. This saves time, as your code doesn't have to slowly walk/search the typeinfo to find what must be done for each member field. This is a possibility for manual optimization many people (including me) have asked for already. And it opens up new possibilities, like e.g. RAII. If you combine it with inline declared, block local variables, there are many possibilities.
  24. Rudy Velthuis

    Inline Variables Coming in 10.3

    The type is still safe. It just doesn't have to be repeated (IOW, you get DRY). So instead of var X: TObjectList<MyUsefultype>; begin X := TObjectList<MyUsefulType>.Create; You simply do: begin var X := TObjectList<MyUsefulType>.Create; The compiler does not guess. Type inference is not new either. You already had it for true constants and for certain generic functions (if the compiler could determine the type parameter, you don't have to specifiy it). You also had it when declaring a variable using Refactoring. Now you have it in the compiler. They type is not "guessed", it is determined. Also, if you do: var i := 17; then i is not a byte. The compiler chooses a useful type, in this case Integer. If you want something else but the default, you can do: var i: UInt32 := 17; so then you don't use type inference. It makes declaring types a lot simpler and doesn't require repetition, which means there is less chance for errors. I wish we had had it much earlier. These things: inline declaration, inline initialization, block local scope and type inference are all one new feature and belong to each other.
×