Po co komu `new` w językach programowania?

0

Jeśli jesteś programistą C++ i spadłeś z krzesła czytając tytuł -> nie przejmuj się. Mówiąc o językach programowania nie mówiłem o C++.

Jeśli jesteś programistą C#/Java/JavaScript/Itd/Etc i spadłeś z krzesła czytając tytuł... No właśnie. Po co nam słowo kluczowe new?
Rozumiem konieczność new w C++ - służy do tworzenia obiektu dynamicznie, to znaczy mając klasę

class Foo { }

Możemy stworzyć jej obiekt na heap w taki sposób:

Foo *f = new Foo()

Tylko że równie dobrze (a często lepiej) możemy stworzyć (na stosie) tak:

Foo f = Foo()

Do czego zmierzam -> w C#/Javie/JavaScripcie/Itd/Etc nie da się stworzyć obiektu inaczej niż dynamicznie. A nawet jeśli (np. struktury w C#), i tak należy użyć new. Byłby ktoś w stanie mi wytłumaczyć po co? Po co mam pisać dodatkowo cztery znaki (['n', 'e', 'w', ' ']) za każdym razem kiedy wykonuję najczęstszą rzecz w obiektowym języku programowania?

Jeśli jesteś programistką C++/C#/Java/JavaScript/Itd/Etc/Whatever i spadłaś z krzesła czytając tytuł -> specjalnego podpunktu nie napisałem jako że w społeczności programistów znajdujesz się w granicy błędu statystycznego.

PS. A tak poza tym -> wesołego nowego roku (jeśli ktoś czyta ten temat przed pierwszym stycznia rano -> gratuluję braku życia ;) )

0

Też trzeźwy sylwester przed kompem ;p ? Może nie spadłem z krzesła, ale jako programista c++ rzuciłem kur**, a potem zastanawiałem się jaki cwaniak taki temat napisał ... potem przypomniało mi się, że zmieniłeś nick, a potem czytając temat już się zorientowałem o co chodzi. Piszę w C++ i zaczynam lizać C#, ale kompletnie nie znam się na tych nowych językach ;p. Może kwestia tego, że bez new pamieć alokowana jest w momencie uruchomienia programu, a z new dopiero przy wykonywaniu się danej części tego programu ?

0

Imho ma sens w takim kształcie jak to wygląda. Chociaż rzeczywiście sensowne wydawałoby się wywalenie new jako takiego. Zamiast tego dodać & lub * jako referencję.
W końcu zapis Foo a = new Foo(); Foo b = a jakoś trzeba by realizować: Foo a; Foo &b = a;

P.S. pozdrawiam no life'ów :D

0

W Javie jeszcze nie można tworzyć obiektów na stosie (oprócz typów prostych). Ale już niedługo :] (czy przypadkiem ten ficzer nie miał być w wersji 7?). Tak więc uzasadnienie jest.

0

@Lukas -> przykro mi że wyprowadziłem cię z równowagi, ale mam tendencję do zakładania średnio normalnych tematów z głupimi nazwami :)
Faktycznie, czytając swój post sam bym się nie domyślił o co chodzi... Cóż, zaczynam rozumieć (niektórych) zadających pytania w dziale newbie którzy piszą trzy nieskładne zdania i oczekują od innych mocy jasnowidztwa (może to te opary alkoholowe z ulicy...?). Sorry dla tych którzy zmarnowali czas starając się mnie zrozumieć.

__ --> Tak więc ERRATA, WYJAŚNIENIE POSTA <-- __
Chodzi mi o składnię. W C++ new ma głęboki sens i do C++ nic nie mam -> jego użycie zmienia sposób tworzenia obiektu ze statycznego (stos) na dynamiczny (sterta).
W C#/Java/JavaScript/Itd new w sumie nie wiadomo skąd i po co się bierze. Nie mamy wyboru statyczny/dynamiczny bo zawsze jest dynamicznie. Czyli to 'new' jest nadmiarowe. Tak jakby zamiast pisać

void foo() {}
void baz() {}
void bar()
{
    foo();
    baz();
}

trzeba było pisać

void foo() {}
void baz(int i) {}
void bar()
{
    call foo(); // kompletnie do niczego nieprzydatne hipotetyczne słowo kluczowe call
    call baz(2); // które trzeba dodawać przed każdym wywołaniem metody.
    // ma to sens? Nieszczególnie. A dokładnie tak samo jest z new przed konstruktorem.
}

Przyszła mi do głowy zresztą jeszcze jedna rzecz - minimalistyczny w kwestii ilości tokenów Python obchodzi się jakoś bez new - czyli się da.

P.S. pozdrawiam no life'ów :D

Pozdrawiam, pozdrawiam.

0

Escape Analysis w Javie to 100 % automat, nic nie trzeba ręcznie oznaczać. ZTCP w Javie 6 było to w fazie eksperymentalnej, a w Javie 7 jako standard, czy jakoś tak. W każdym razie działa i już kiedyś pokazywałem na forum, że działa.

Wracając do tematu: new jest w ogólności zbędne w językach zarządzanych (no chyba, że służy do innych rzeczy niż rozróżnianie pomiędzy alokacją na stercie i stosie). Taka np Scala pozwala omijać "new" w przypadku case classes.

Jakimś minimalnym wytłumaczeniem dla obecności new'a mógłby być kod:

public class Main {

    public static void main(String[] args) {
        Main main = Main();
        System.out.println(main);
    }
    
    static Main Main() {
        return new Main();
    }
}

Wydaje mi się jednak, że najprawdopodobniej new w Javie jest po to, żeby Java była łatwiejsza do zrozumienia dla C++owców. Kulawa jak na dzisiejsze czasy składnia Javy jest właśnie spowodowana chęcią przyciągnięcia leniwych i ograniczonych umysłowo (tzn. przede wszystkim tej części spośród ogółu) programistów C++ - sam Gosling przyznał, że gdyby miał czas i nie miał parcia na przyciągnięcie owych programistów to język Java przypominałby bardziej dzisiejszą Scalę. Składnia pierwszej wersji Javy była raczej robiona na szybko (nota bene: z wersji na wersję czas debat nad ulepszeniami języka kosmicznie się wydłuża, jest to dobitna wada projektowania rzeczy na szybko). W C# new jest generalnie dlatego, bo C# zaczynał jako marna kopia Javy. A na JavaScripcie się nie znam, ale tam chyba słówko function służy zarówno do deklarowania funkcji jak i konstruktorów i trzeba jakoś rozróżnić pomiędzy tworzeniem obiektu, a wywoływaniem funkcji i słówko new właśnie temu służy, czyż nie?

0

Czy ja wiem czy tak ograniczonych programistów C++, chyba, że mówimy o "programistach", którzy w porywach mogą napisać bardziej rozbudowany hello world i nie mają pojęcia co tak naprawdę w tym się dzieje - takich nie ma po co przyciągać.
C# ma struktury, więc tu jeszcze można jakoś wytłumaczyć to new(kulawo)

Pozdrawiam następnego no life'a :D

0

Fajerwerki z dymem poszły, do przyziemnych zajęć można wracać.
Jako że wypiłem sporo, wybaczcie jeśli bełkoczę bzdury jakieś ;-)

Przyznam, że nad new się specjalnie nie zastanawiałem, chociaż dziwnie mi się go pisze w C# w przypadku struktur.
Ażeby było trudniej, w C++/CLI można zainicjować zmienną klasy zarządzanej z pominięciem new, co ma sugerować tworzenie jej na stosie, tyle że… nie jest na stosie, tylko jak zawsze na stercie, tyle że niejawnie.

Bardziej przeszkadza mi w językach C++-podobnych podwójne pisanie typu w tej samej linijce:

JakasKlasa zmienna = new JakasKlasa();

Czyż to nie nadmiarowa informacja?

Na szczęście w C# w pewnym momencie złapano się za głowę i odtąd można użyć słowa var zamiast pierwszej nazwy typu:

var zmienna = new JakasKlasa();

co da dokładnie taki sam efekt jak pełna linijka powyżej. Oczekiwałem tego w C++, i oto jest w C++11, tyle że ze słowem auto:

auto zmienna = new JakasKlasa();
0

Imo to nie jest nadmiarowa informacja tylko uściślenie, że robisz to co rzeczywiście chcesz. Może przecież być w cpp podobnych taka konstrukcja JakaKlasaT zmienna = new JakaśPochodnaOdT();

0

Żeby się debile nie myliły. Czyli tania siła robocza z trzeciego świata. Przypominam o break w switchu w c#...

0

to właśnie new dla struct w C# wprowadza zamieszanie, bo nie można zapominać co jest klasą a co strukturą (inne znaczenie operatora przypisania) — więc inna składnia tworzenia obiektu „przypominałaby” co jest czym.

A break? Wydaje się nadmiarowe, ale jak się przyjrzeć, to warunkiem nie jest umieszczenie break, a tylko by sterowanie nie „spadło” do bloku niżej.
Przykład innego niż break zakończenia bloku:

    for (;;)
      switch (i)
      {
         case 1 : goto case 3; // tak można zasymulować fall-through
         case 2 : continue; // dotyczy zewnętrznego fora
         case 3 : return; // dotyczy funkcji
         case 4 : while(true); // kompilator widzi że ten case nigdy się nie kończy
      }

oczywiście mogliby zrobić, że brak break oznacza tak jakby tam był, ale widać nie chciano odchodzić zbyt od składni C++.

0

jak rozwiązać taką niejednoznaczność w przypadku gdy nie stosuje się 'new' przy tworzeniu obiektów? Wymuszając this. przy dostępie do wszystkich pól i metod klasy? To będzie jeszcze więcej pisania niż zaoszczędzimy usuwając new

class Foo
{
    void TworzenieObiektuCzyWywolanieMetody()
    {
        /*new*/ Bar();
    }

    void Bar() { }
}

class Bar { }
0

zaciemnia (tak to się na polski tłumaczy?
tłumaczy się „przysłania”.
w C# do takiej klasy można się dostać przez pełny namespace, a jeśli nie zdefiniowano żadnego, globalną przestrzeń nazw global:

this.Bar();      // metoda
global::Bar foo; // typ - zmienna
0

Bardziej przeszkadza mi w językach C++-podobnych podwójne pisanie typu w tej samej linijce:

JakasKlasa zmienna = new JakasKlasa();

Oprócz wspomnianego Haskella, Scala też ma całkiem sprawną inferencję typów:

val zmienna = new JakasKlasa
val zmienna2 = wolajFunkcje

def wolajFunkcje = {
  val zm = new JakasKlasa
  zm.x = 123
  zm
}

Co do break w switch, to zamiast umożliwiać fallthrough, wystarczy dać większą dowolność dla możliwych wartości w case:

switch (i) {
    case 1:
    case 2: cośtam1; break;
    case 3: cośtam2; break;
    case 4: cośtam3;
}
switch (i) {
    case 1 | 2: cośtam1;
    case 3: cośtam2;
    case 4: cośtam3;
}

Albo w ogóle prymitywnego switcha wymienić na pattern matching (który btw kompilator Scali potrafi zamienić na switcha, jeśli to możliwe).

0

Tak jest czytelniej - od razu widzę że tworzony jest obiekt, a nie wywoływana jest funkcja.
O ile zapis:
Foo f = Foo()
jest w miarę czytelny, to
Func(Foo()) już nie jest moim zdaniem takie oczywiste jak Func(new Foo()), zwłaszcza jak nazwa klasy nie jest jednoznaczna.
Czyli jak ktoś napisał -> aby debile się nie myliły ;)

0

Mogę się wypowiedzieć nt. JavaScriptu z racji mojej znajomości tego języka.

W JavaScripcie, new F() to coś innego niż F(). Oba są wywołaniami funkcji, ale operator new, oprócz wywoływania funkcji, robi coś ekstra: ustawia kontekst (this) wewnątrz F, pilnuje, by F zwróciło obiekt i umożliwia utworzenie obiektu dziedziczącego po prototypie.

JavaScript to język dynamiczny. Nie da się statycznie wywnioskować, że pisząc F() chcemy wywołać konstruktor -- może po prostu chcemy wywołać funkcję F? Wiele funkcji pisze się tak, że można je wywołać zarówno z new, jak i bez new i efekt będzie ten sam. Od tej reguły są jednak wyjątki. Nawet wśród funkcji wbudowanych. Np. funkcja Date(). Wywołana z new zwraca obiekt. Bez new -- ciąg znaków.

To powiedziawszy, operator new można uznać za jeden z błędów projektowych JavaScriptu. new zostało dodane do języka po to, by upodobnić się do języków z dziedziczeniem klasycznym, w szczególności do Javy. To błąd, bo JavaScript nie posiada dziedziczenia klasycznego. Nie udostępnia programiście takiego pojęcia jak "klasa". Specyfikacja języka definiuje co prawda ukrytą, roboczą własność [[Class]], ale nazywa jej wartość "klasyfikacją obiektu", a nie klasą.

JavaScript, zamiast dziedziczenia klasycznego, ma dziedziczenie prototypowe. Jest ono prostsze i -- można się kłócić -- bardziej eleganckie, a na pewno minimalistyczne. Nie ma tu podziału na "obiekty" (instancje) i jakieś ezoteryczne "klasy". Nie. W JavaScripcie są tylko obiekty. Obiekty dziedziczą po obiektach-prototypach. I tyle.

Gdzie tu miejsce dla new?

No... właśnie specjalnie go nie ma.

Teoretycznie (jak zaleca np. Douglas Crockford), gdy tworzymy obiekt, powinniśmy powiedzieć: daj mi pusty obiekt myObj dziedziczący po obiekcie myProto (jak "prototype"). W ECMAScript 5 wprowadzono funkcję Object.create, która pozwala nam zrobić właśnie to (pomijam w tym poście pozostałe funkcje Object.create):

var myObj = Object.create(myProto);

I dzięki tej małej funkcji, operator new robi nam się absolutnie niepotrzebny. Wywołanie Object.create możemy ukryć w naszych "konstruktorach", czy też "metodach wytwórczych" (że pozwolę je sobie tak nazwać):

var programmerPrototype = {
  // definicje funkcji i pól składowych wspólnych dla wszystkich programistów
  writeCode: function() {
    // ...
  }
};

function createProgrammer(nameArg, languageArg) {
  var that = Object.create(programmerPrototype);
  // różnice pomiędzy programistami
  that.name = nameArg;
  that.language = languageArg;
  return that;
}

// tworzenie instancji
var uncleBob = createProgrammer("Robert", "Java");
uncleBob.writeCode();

Powyższy wzorzec jest prosty, ale bardzo klasyczny -- prototyp jest stały i wygląda z grubsza jak definicja klasy. Można go równie dobrze napisać, nie używając Object.create i używając operatora new. Poniżej ekwiwalent przykładu z Object.create:

Programmer.prototype = {
  // definicje funkcji i pól składowych wspólnych dla wszystkich programistów
  writeCode: function() {
    // ...
  }
};

function Programmer(nameArg, languageArg) {
  // różnice pomiędzy programistami
  this.name = nameArg;
  this.language = languageArg;
}

// tworzenie instancji
var uncleBob = new Programmer("Robert", "Java");
uncleBob.writeCode();

Czyli: tu new nie jest takie złe, bo każdy programista ma prototyp, który jest dokładnie tym samym obiektem.

W bardziej skomplikowanym kodzie dość często zdarza mi się jednak stosować dynamicznie tworzone prototypy lub nawet ich łańcuchy. Chcę móc dynamicznie dobierać rodziny obiektów, każda z własnym prototypem.

Tu już notacja z new oraz Konstruktor.prototype jest cholernie niewygodna i pokraczna. Muszę użyć czegoś w rodzaju Object.create. Ponieważ nie wszystkie przeglądarki mają Object.create, muszę sobie taką funkcję napisać...

function createWithPrototype(proto) {
  function Dummy() {
  }
  Dummy.prototype = proto;
  return new Dummy();
}

// wywołanie:
var myObj = createWithPrototype(myProto);

Tutaj widać, że Object.create i new są w JavaScripcie ekwiwalentne (a tak naprawdę Object.create jest potężniejsze, bo jako drugi parametr może przyjąć tzw. deskryptor obiektu -- ale mniejsza o to). Czyli: new jest w nowoczesnym JavaScripcie zbędne. Crockford zdaje się sugerować, by i w starym JavaScripcie użyć new tylko raz: pisząc createWithPrototype() ;).

Normalnie, gdy ktoś napisał funkcję z myślą o tym, by była używana z new, a my o new zapomnimy (silnik JS o tym nie ostrzeże, bo new nie zawsze jest obowiązkowe!), to stanie się tragedia.

Załóżmy, że pominęliśmy new wywołując zdefiniowany wyżej konstruktor Programmer:

var bob = Programmer("Kmieciu", "Delphi");

Wewnątrz funkcji Programmer doczepiamy jakieś własności do obiektuthis. Jeśli funkcja jest wywołana z new, this wskazuje w niej na nowo utworzony obiekt (czyli programistę). Jeśli new pominiemy, to mamy zwykłe wywołanie funkcji globalnej. I this wskazuje na obiekt globalny, którym w przeglądarce jest window. Więc nasz konstruktor (wywołany jak zwykła funkcja globalna), zamiast pól obiektu bob, tworzy pola obiektu window: window.name i window.language, czyli zmienne globalne name i language. Tragedia.

Gdybyśmy korzystali z Object.create, bylibyśmy bezpieczni.

0

bswierczynski jak zwykle nie zawiódł oczekiwań, dostaliśmy długi i treściwy elaborat, +1! :)
Ja od siebie dodam sposób, o którym nikt nie wspomniał: utworzenie obiektu poprzez niby statyczną metodę klasy, jak jest w Object Pascalu:

var btn : TButton;
begin
  btn := TButton.Create();
  btn.Name := 'alamakota';
end

albo Ruby:

obj = Klasa.new
# voila!
0

A po co w ogóle używać new? Nie lepiej użyć kontenera DI i scedować na niego tworzenie obiektów?

Swoją drogą jeżeli dopuszczamy w języku konstrukcie typu:

public class A {

	public static void main(String[] args) {
		A A = A();
	}

	public A() {
	}

	public static A A() {
		return new A();
	}

}

to operator new zaczyna być bardzo pomocny by zrozumieć kod. Inaczej zaczynamy wpadać w pułapki opisane przez bswierczynski.

0

Mi sie wydaje ze new jest po prostu po to aby ulatwic parsowanie kodu, usunac zaleznosci od kontekstu itp itd. Python np. nie ma wcale new, i jakos dziala. Jak juz Vit wspomnial, case classes w Scala rowniez, Ceylon nie ma tego wcale (nowy jezyk na JVM tworzony pod patronatem JBossa), nie wiem Kotlin (JetBrains) ale wyglada ze trend jest ku temu aby jednak new nie bylo.

1 użytkowników online, w tym zalogowanych: 0, gości: 1